I was looking into an open source pull-to-refresh control and it swizzle lifecycle methods on a UIViewController category like so:
- (void)INBPullToRefreshView_viewWillAppear:(BOOL)animated
{
[self setClearNavigationBar:YES];
[self INBPullToRefreshView_viewWillAppear:animated];
UITableView *tableView = self.pullToRefresh.tableView;
tableView.contentOffset = tableView.contentOffset;
self.pullToRefresh.showPullToRefresh = YES;
}
I get that when viewWillAppear was called it mapped to the above method, and that calling [self INBPullToRefreshView_viewWillAppear:animated]; will map to the original viewWillAppear.
However, what does the following do?:
tableView.contentOffset = tableView.contentOffset;
Here's the github source for the control.
I would suspect the author is trying to use a side-effect of setContentOffset:, perhaps forcing a recalculation. But the author seems active on the project, so why not ask intmain in a github issue?
Of course the standard warnings that this kind of method swizzling is extremely dangerous and fragile apply.
I believe you're asking something unrelated to the swizzling itself?
Setting the contentOffset property will cause a scrollViewDidScroll: message sent to the delegate of your object. There's probably a cleaner way to accomplish that (or at least it should have a comment)
Related
My NSDocument subclass implements selectAll:. Only problem is, I'm using NSTableView, and it also implements selectAll:. However, the selectAll: action in NSTableView doesn't do what I want, and it does prevent the selectAll: method in my Document class from ever being reached in the responder chain.
I already have a subclass of NSTableView, and after poking around a bit I got things working the way I want by adding a respondsToSelector: method to my NSTableView subclass which lies to the runtime by telling it there is no selectAll: action:
-(BOOL)respondsToSelector:(SEL)targetSelector
{
if (targetSelector == #selector(selectAll:)) {
return FALSE; // we don't want tableView's implementation of selectAll
}
return [super respondsToSelector:targetSelector];
}
This seems to work fine, allowing the selectAll: method in my document subclass to do its thing. But this solution leaves me a bit uneasy. What about other action methods I have implemented in this subclass? Do I need to manually check and return true for each of them? I do have two actions defined in this subclass, moveLeft: and moveRight:, and they seem to work, even though I am not handling them in respondsToSelector:. So my question is, am I doing this correctly, or is there something I am missing? Or perhaps there is some entirely different way to do this properly?
By the way, I got the idea of overriding respondsToSelector from this post on the OmniGroup forum:
http://mac-os-x.10953.n7.nabble.com/Removing-an-action-from-a-subclass-td27045.html
Sending a message to super affects which implementation of that method we use. It doesn't change who self is.
So let's try to imagine how respondsToSelector: works. Given a selector mySelector, it probably introspects every class up the superclass chain, starting with [self class], to see whether it actually implements mySelector.
Now then, let's say your subclass is called MyTableView. When MyTableView says
[super respondsToSelector:targetSelector]
what happens? The runtime will look up the superclass chain for another implementation of respondsToSelector:, and eventually will find NSObject's original implementation. What does that implementation do? Well, we just answered that: it starts the search for an implementation of targetSelector in [self class]. That's still the MyTableView class! So if you have defined moveLeft: in MyTableView, respondsToSelector: will find it and will return YES for moveLeft:, exactly as you hope and expect.
Thus, to generalize, the only selector for which this search has been perverted is the search for selectAll: - exactly as you hope and expect. So I think you can relax and believe that what you're doing is not only acceptable and workable but the normal solution to the problem you originally posed.
You might also like to look at the Message Forwarding chapter of Apple's Objective-C Runtime Programming Guide.
I currently have a UIViewController and a NSObject class.
What i want to do is to tell the NSObject class to perform an action and then tell the UIViewController when it has finished it's action.
I'm calling the object to perform it's action like so:
[fooObject performActionWithDelegate:self];
The performActionWithDelegate function basically only takes the UIViewcontroller's delegate to perform a callback.
-(void)performActionWithDelegate:(id)d{
// bar is declared in the fooObject header file
// id bar;
[bar setDelegate:d];
[bar performCallback];
}
Where performCallback is a simple NSLog()-statement in the UIViewController:
-(void)performCallback{
NSLog(#"Successfully performed a callback");
{
Now, i'd like this to work. My first guess is that this is not the best approach to this problem.
The full scope of the problem is that the fooObject is supposed to perform a httppost to a webservice to update one of it's properties and then inform the uiviewcontroller if the operation was successful or not.
How do i achieve this?
Any tips and/or pointers will be highly appreciated!
Thanks in advance.
Edit:
The actual problem is that the fooObject is not performing the callback.
Its not clear exactly what you are trying to accomplish. What you are doing seems related to two different design patterns:
Delegate
Asynchronous callback
Delegate: You would use a delegate if there is some reason to separate out some of the functionality of your UIViewController into another object. Instead of the UIViewController doing something it asks another object to do it. This is commonly used for code reuse so you can have the same UIViewController serve in different cases and just change the delegate to change some of its behavior.
Asynchronous callback: This allows an operation to occur in the background while you are doing other things and then be notified by calling a method of your object when the operation completes. You can do this without involving other objects.
In your case, why do you want to perform an HTTP post to a web service outside of our UIViewController? Do you just want to separate the network code from UI code? In this case, you don't really need a delegate, just call the method on the other object from your UIViewController and when it returns, its done. It can pass back any result you need in other parameters. Returning values by setting properties on the calling object is not generally a very good design. Even if you do this the UIViewController isn't really a "delegate".
On the other hand if you are concerned about blocking the main thread while the HTTP post is in process then you will want to use something like asynchronous callback. The easiest way to do this is to use Grand Central Dispatch. Conceptually you could do something like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self doLongHTTPPost];
dispatch_async(dispatch_get_main_queue(), ^{ [self longHTTPPostDone]; });
});
This will call doLongHTTPPost in the background and then at some later time after it is complete it will call longHTTPPostDone on the main thread where it is safe to take UI actions.
I don't know if it's possible for me to include code here that's relevant as my project is so large but are there any typical reasons why NSLog would repeat some warnings and calls to it at occasions where only one call/error is occuring?
As an example, I have a subclass of NSBox that inits an instance of another class on awakeFromNib:
- (void) awakeFromNib {
burbControllerInstance = [[BurbController alloc] init];
if (burbControllerInstance) {
NSLog(#"init ok");
}
}
I get NSLog printing "init ok" twice. I don't see why this subclass would be 'awoken' twice anywhere in my project. This is part of a larger problem where I can't get variables to return anything but nil from the class I'm creating an instance of. I'm wondering if perhaps the double values are something to do with it.
This post could be helpful, i. e. one comment:
Also important: awakeFromNib can be
called multiple times on the
controller if you use the same
controller for several nibs – say,
you’re using the app delegate as the
owner of both the app’s About Box and
preferences dialog. So you’ll need an
extra guard test if you use
awakeFromNib for anything but
initializing the nib objects
Update: Much more interesting could also be this, where the author mentions that awakeFromNib gets called twice. Unfortunately there is no real answer for this particular problem but maybe some basic ideas.
Update #2: Another potential solution from stackoverflow.com: View Controller calls awakeFromNib twice.
Can I intercept a method call in Objective-C? How?
Edit:
Mark Powell's answer gave me a partial solution, the -forwardInvocation method.
But the documentation states that -forwardInvocation is only called when an object is sent a message for which it has no corresponding method. I'd like a method to be called under all circumstances, even if the receiver does have that selector.
You do it by swizzling the method call. Assuming you want to grab all releases to NSTableView:
static IMP gOriginalRelease = nil;
static void newNSTableViewRelease(id self, SEL releaseSelector, ...) {
NSLog(#"Release called on an NSTableView");
gOriginalRelease(self, releaseSelector);
}
//Then somewhere do this:
gOriginalRelease = class_replaceMethod([NSTableView class], #selector(release), newNSTableViewRelease, "v#:");
You can get more details in the Objective C runtime documentation.
Intercepting method calls in Objective-C (asuming it is an Objective-C, not a C call) is done with a technique called method swizzling.
You can find an introduction on how to implement that here. For an example how method swizzling is implemented in a real project check out OCMock (an Isolation Framework for Objective-C).
Sending a message in Objective-C is translated into a call of the function objc_msgSend(receiver, selector, arguments) or one of its variants objc_msgSendSuper, objc_msgSend_stret, objc_msgSendSuper_stret.
If it was possible to change the implementation of these functions, we could intercept any message. Unfortunately, objc_msgSend is part of the Objective-C runtime and cannot be overridden.
By googling I found a paper on Google Books: A Reflective Architecture for Process Control Applications by Charlotte Pii Lunau. The paper introduces a hack by redirecting an object's isa class pointer to an instance of a custom MetaObject class. Messages that were intended for the modified object are thus sent to the MetaObject instance. Since the MetaObject class has no methods of its own, it can then respond to the forward invocation by forwarding the message to the modified object.
The paper does not include the interesting bits of the source code and I have no idea if such an approach would have side effects in Cocoa. But it might be interesting to try.
If you want to log message sends from your application code, the -forwardingTargetForSelector: tip is part of the solution.
Wrap your object:
#interface Interceptor : NSObject
#property (nonatomic, retain) id interceptedTarget;
#end
#implementation Interceptor
#synthesize interceptedTarget=_interceptedTarget;
- (void)dealloc {
[_interceptedTarget release];
[super dealloc];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(#"Intercepting %#", NSStringFromSelector(aSelector));
return self.interceptedTarget;
}
#end
Now do something like this:
Interceptor *i = [[[Interceptor alloc] init] autorelease];
NSFetchedResultsController *controller = [self setupFetchedResultsController];
i.interceptedTarget = controller;
controller = (NSFetchedResultsController *)i;
and you will have a log of message sends. Note, sends sent from within the intercepted object won't be intercepted, as they will be sent using the original object 'self' pointer.
If you only want to log messages called from the outside (usually called from delegates; to see which kind of messages, when, etc.), you can override respondsToSelector like this:
- (BOOL)respondsToSelector:(SEL)aSelector {
NSLog(#"respondsToSelector called for '%#'", NSStringFromSelector(aSelector));
// look up, if a method is implemented
if([[self class] instancesRespondToSelector:aSelector]) return YES;
return NO;
}
Create a subclass of NSProxy and implement -forwardInvocation: and -methodSignatureForSelector: (or -forwardingTargetForSelector:, if you're simply directing it on to a second object instead of fiddling with the method yourself).
NSProxy is a class designed for implementing -forwardInvocation: on. It has a few methods, but mostly you don't want them to be caught. For example, catching the reference counting methods would prevent the proxy from being deallocated except under garbage collection. But if there are specific methods on NSProxy that you absolutely need to forward, you can override that method specifically and call -forwardInvocation: manually. Do note that simply because a method is listed under the NSProxy documentation does not mean that NSProxy implements it, merely that it is expected that all proxied objects have it.
If this won't work for you, provide additional details about your situation.
Perhaps you want NSObject's -forwardInvocation method. This allows you to capture a message, retarget it and then resend it.
You can swizzle the method call with one of your own, which does whatever you want to do on "interception" and calls through to the original implementation. Swizzling is done with class_replaceMethod().
A method call, no. A message send, yes, but you're going to have to be a lot more descriptive if you want a good answer as to how.
To do something when a method is called, you could try an events based approach. So when the method is called, it broadcasts an event, which is picked up by any listeners. I'm not great with objective C, but I just figured out something similar using NSNotificationCenter in Cocoa.
But if by "intercept" you mean "stop", then maybe you need more logic to decide wether the method should be called at all.
Is there a way to observe changes in derived properties? For example, I want to know when a CALayer has been added as a sublayer so that I can adjust its geometry relative to its (new) parent.
So, I have a subclassed CALayer, say CustomLayer, and I figured I could register an observer for the property in init:
[self addObserver:self forKeyPath:#"superlayer" options:0 context:nil]
and implement observeValueForKeyPath:ofObject:change:context. Nothing ever happens because, presumably, superlayer is a derived property (the attr dictionary stores an opaque ID for the parent). Similarly, I can't subclass setSuperlayer: because it is never called. In fact, as far as I can tell there are no instance methods called or public properties set on the sublayer when a parent does [self addSublayer:aCustomLayer].
Then I thought, OK, I'll subclass addSublayer like this:
- (void)addSublayer:(CALayer *)aLayer {
[aLayer willChangeValueForKey:#"superlayer"];
[super addSublayer:aLayer];
[aLayer didChangeValueForKey:#"superlayer"];
}
but still nothing! (Perhaps it's a clue that when I make a simple standalone test class and use the will[did]ChangeValueForKey: then it works.) This is maybe a more general Cocoa KVO question. What should I be doing? Thanks in advance!
Well, superlayer is defined as a readonly property, which means that there's no setSuperlayer: method. (If there is, it would be private, and you probably shouldn't use it.) If I had to make a guess, it would be that the superlayer property just isn't KVO-compliant. And, aside from that, I generally don't think it's a good idea for classes to observe themselves.
Maybe there's another way of doing this. When a layer is added to a superlayer, the onOrderIn action takes place. Now, actionForKey: is an instance method that gives a layer an opportunity to customize the default animations for certain properties. You could override actionForKey: to detect when the onOrderIn action takes place, do your thing, then call super's implementation.
I consider this a pretty messy hack, too, though. But it should be a bit more "self-contained" than having to use custom layers for everything and messing with KVO messages.
[self addObserver:self forKeyPath:#"superlayer" options:0 context:nil]
Don't observe yourself through KVO. Change your accessors instead.
Similarly, I can't subclass setSuperlayer: because it is never called.
I take it you tried this and added NSLog and found that it wasn't called?
Then I thought, OK, I'll subclass addSublayer like this:
- (void)addSublayer:(CALayer *)aLayer {
[aLayer willChangeValueForKey:#"superlayer"];
[super addSublayer:aLayer];
[aLayer didChangeValueForKey:#"superlayer"];
}
And the parent layer is also a CustomLayer, right? If the parent layer is a plain CALayer, anything you do in CustomLayer will have no effect.