tags:

views:

337

answers:

4

Occasionally, it's useful to set up method dispatch table so calling method A calls AImpl() method while calling method A at another time calls BImpl(). How can this be done in Objective-C for a method that's automatically called by the system (e.g. delegate methods)?

For example, if the sytem calls viewDidAppear for the first time, I want it to call viewAppearFirstTime while calling viewDidAppear subsequently will hit a totally different method body (instead of doing if-else check within the code using boolean flag).

Another example, say UIView's drawRect is being called very often in an app, if the drawRect called for the first time is different than subsequent ones, I don't want to include an if-test because that's the code harder to read and the check is also unnecessary after the first time.

+2  A: 

Read this, then put your NSInvocation instances inside an NSDictionary.

slf
+2  A: 

The Objective-C runtime allows you to modify the mapping from a selector to a method implementation at runtime, and it's pretty slick. Check out this article on CocoaDev for more information: CocoaDev: Method Swizzling. You can swizzle any function you have the existing mapping for, so you effectively replace any function you want in the obj-c runtime.

It's not really something you should do everywhere though - I'd try NSInvocations first. Since you can write your own viewDidAppear function and call out to your viewAppearFirstTime: function from within that, method swizzling is probably overkill.

Good luck!

Ben Gotow
+2  A: 

If you want to modify the method for all instances of the class, you can do it via method swizzling. I've never heard of it being used for this purpose, but you may be able to accomplish what you want using class_replaceMethod from within your viewDidAppearFirstTime.

However, if you want to modify the method dispatch for a single instance, I think your only option is a conditional statement within your viewDidAppear method or to consider a redesign that does not require separate methods on first and subsequent appearances (from your description, separating your code into viewDidLoad and viewWillAppear functions seems to make sense).

Barry Wark
+3  A: 

I'll caveat here: the particular case you're describing probably shouldn't be done this way. It's almost certainly over-clever and should probably be separated into viewDidLoad and viewWillAppear rather than two versions of viewWillAppear. That said, the problem does come up.

My preferred solution to this is to keep a SEL that points to what I want to do. For instance, I use this kind of technique for a "next action" after a complicated asynchronous activity:

if (self.nextActionSelector != NULL)
{
    [self performSelector:self.nextActionSelector];
}

So for this, you could use a viewWillAppearSelector that would be changed over time, and viewWillAppear would just call whatever it points to.

Ben's recommendation of Method Swizzling is useful in some cases, but more often when you're trying to modify the behavior of some existing object rather than yourself. It can really make debugging fun.... OK, not actually fun. Painful. Very painful.

Beyond that, I'd look at -forwardInvocation:, which can be used to respond to messages you don't directly implement. You can then rewrite the invocation to call the method you really want. This isn't really the way it's meant to be used, though. It's for forwarding calls to other objects transparently. But at least debugging it isn't that difficult since the debugger will go where you expect it to.

Generally in this case, rather than a boolean, I look for the evidence that I've been run before. Checking if view is set already, or some other value that is initialized the first time so I don't need a special variable and so I'm resilient to things that clear my state (like iPhone's practice of dumping things when memory is tight). Where possible, I make these things self-initialize in their getters rather than having some external party have special first-time logic. Keeping the logic here simple makes maintenance much easier, and hey, NSInvocation is crazy-slow (ok, you wouldn't notice it except in a tight loop, but it is about 500x slower than a method call in my tests. That said, I'm not suggesting making this decision on performance, just maintainability).

Rob Napier