Next article: Friday Q&A 2009-04-03: On Hold
Previous article: Friday Q&A 2009-03-20: Objective-C Messaging
Tags: fridayqna objectivec
Welcome back to another exciting Friday Q&A. This week I'm going to continue the series on the Objective-C runtime. Yuji Tachikawa suggested talking about how @dynamic
properties work in CoreData and I'm going to take that and expand it to talk about message forwarding in general.
No Such Method
Last week I talked about how Objective-C messaging works, and mentioned that interesting things happen when no method is found for a given selector. Those interesting things are what make forwarding happen.
(If you aren't totally clear on what a selector is or what the difference is between a method and a message, you might want to go read through that article real quick, or at least re-read the Definitions section if you already read it.)
Just what is message forwarding? Simply speaking, it allows unknown messages to be trapped and reacted to. In other words, any time an unknown message is sent, it gets delivered to your code in a nice package, at which point you can do whatever you like with it.
This kind of thing is incredibly powerful and allows for doing all kinds of nifty, clever things.
What Happens
What happens when you do [foo bar]
and foo
doesn't implement a bar
method? When it does implement such a method, it's pretty straightforward: it looks up the appropriate method, then jumps to it. When no such method can be found, a complicated sequence of events ensues:
- Lazy method resolution. This is done by sending
resolveInstanceMethod:
(resolveClassMethod:
for class methods) to the class in question. If that method returns YES, the message send is restarted under the assumption that the appropriate method has now been added. - Fast forwarding path. This is done by sending
forwardingTargetForSelector:
to the target, if it implements it. If it implements this method and it returns something other thannil
orself
, the whole message sending process is restarted with that return value as the new target. - Normal forwarding path. First the runtime will send
methodSignatureForSelector:
to see what kind of argument and return types are present. If a method signature is returned, the runtime creates anNSInvocation
describing the message being sent and then sendsforwardInvocation:
to the object. If no method signature is found, the runtime sendsdoesNotRecognizeSelector:
.
Lazy Resolution
As we learned last week, the runtime sends messages by looking up a method, or IMP
, and then jumping to it. Sometimes it can be useful to dynamically plug IMPs into a class instead of setting them all up beforehand. Doing this allows for really fast "forwarding", because after the method is resolved, it gets invoked as part of the normal message sending process. The disadvantage is, of course, that this isn't very flexible, since you need to have an IMP ready to plug in, and that in turn means that you need to have already anticipated the argument and return types that will be arriving.
This kind of thing is great for stuff like @dynamic properties. The method signature is something you should know in advance: you'll either take one parameter with a void return, or have no parameters and return one value. The types of the values will vary, but you can cover the common cases. Since the IMP gets passed the selector that's been sent to the object, it can use that selector to get the name of the property and look it up dynamically. Plug it in to the class using +resolveInstanceMethod:
and off you go.
Fast Forwarding
The next thing the runtime does is see if you want to just send the whole message unchanged to a different object. Since this is a common case of forwarding, this allows it to be done with minimal overhead.
For some reason, fast forwarding is really poorly documented. The only place Apple even mentions it, aside from a commented-out declaration in NSObject.h
, is in the Leopard release notes. (Search for "New forwarding fast path".)
This technique is great for faking multiple inheritence. You can write a little override like this:
- (id)forwardingTargetForSelector:(SEL)sel { return _otherObject; }
_otherObject
, which will make your object appear from the outside as though it combined your object with this other object in one.
Normal Forwarding
The first two are basically just optimizations that allow forwarding to go faster. If you don't take advantage of them, the full forwarding mechanism goes into action. This creates an NSInvocation
object which fully encapsulates the message being sent. It holds the target, the selector, and all of the arguments. It also allows full control over the return value.
Before the runtime can build the NSInvocation
it needs an NSMethodSignature
, so it requests one using -methodSignatureForSelector:
. This is required due to Objective-C's C heritage. In order to bundle the arguments up in the NSInvocation
, the runtime needs to know what kind of arguments there are, and how many of them there are. This information isn't normally provided in a C runtime environment, so it has to do an end run around the C "bag of bytes" view of the world and get that type information in another way.
Once the invocation is constructed, the runtime then invokes your forwardInvocation:
method. From there you can do whatever you want with the invocation it hands you. The possibilities are endless.
Here's one quick example. Imagine you're tired of writing loops, so you want to be able to manipulate arrays more directly. Add this little category to NSArray
:
@implementation NSArray (ForwardingIteration)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
NSMethodSignature *sig = [super methodSignatureForSelector:sel];
if(!sig)
{
for(id obj in self)
if((sig = [obj methodSignatureForSelector:sel]))
break;
}
return sig;
}
- (void)forwardInvocation:(NSInvocation *)inv
{
for(id obj in self)
[inv invokeWithTarget:obj];
}
@end
[(NSWindow *)windowsArray setHidesOnDeactivate:YES];
NSProxy
.
NSProxy
is basically a class that's expilicitly designed for proxying. It implements a minimal subset of methods, leaving everything else up for grabs. This means that a subclass that implements forwarding can capture basically any message.
To use NSProxy
for this kind of thing, you'd write an NSProxy subclass that can be initialized to point at an array, and then add a little stub method to NSArray
that returns a new instance of the proxy, like so:
@implementation NSArray (ForwardingIteration)
- (id)do { return [MyArrayProxy proxyWithArray:self]; }
@end
[[windowsArray do] setHidesOnDeactivate:YES];
Declarations
Another consequence of Objective-C's C heritage is that the compiler needs to know the full method signature of every message that you're going to send in your code, even purely forwarded ones. To make a contrived example, imagine writing a class that uses forwarding to produce integers from code, so that you can write this:
int x = [converter convert_42];
The trouble is that the compiler doesn't know about any convert_42
method, so it has no idea what kind of value it returns. It will give you a nasty warning, and will assume that it returns id
. The fix to this is simple, just declare one somewhere:
@interface NSObject (Conversion)
- (int)convert_42;
- (int)convert_29;
@end
Conclusion
Message forwarding is a powerful technique that greatly multiplies the expressiveness of Objective-C. Cocoa uses it for things like NSUndoManager
and distributed objects, and it can let you do a lot of nifty things in your own code.
That wraps up this week's Friday Q&A. Tune in next week for more riveting tales of programming, and leave your comments on this edition below.
Friday Q&A is powered by the contribution of your ideas. If you have an idea you'd like to see discussed here, post it in the comments or e-mail it directly. I will use your name unless you ask me not to.
Comments:
Anyone else tried something similar? I'm interested in any experiences with that approach.
Great article btw.
Sijmen, there are lots of common code examples that use the method forwarding system to record methods in NSInvocations (to "forward" them later).
The NSUndoManager does this to record undo invocations. I wrote a post a while ago on how you can do this in your own code: http://cocoawithlove.com/2008/03/construct-nsinvocation-for-any-message.html
[1] http://developer.apple.com/DOCUMENTATION/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
I don't see where Apple says that type encodings can change. Generally, if they give a list like that, they mean for it to be something you can rely on. More concretely, since these strings get generated by the compiler and hardcoded into the binary, it would be impossible for them to make changes without destroying all sorts of existing stuff.
One thing to clarify is that you also need to implement respondsToSelector, especially in the case of a delegate where the methods safe generally optional. If you don't implement respondsToSelector, the caller will not try to execute the forwarded methods.
Is there any way to forward class message to object? For example
+ [MyClass test] should be forwarded to - [someObject test].
What i'm actually trying to implement is singleton with useful class methods declared in protocol, and if user send class message it should be forwarded to shared instance.
I've actually did it when read about meta-class, just after i asked this question. Objective-C doesn't stop to surprise me!
I put together a proxy class implementation for iOS 6.0 SLComposeViewController class. It demonstrates a complete proxy code that forwards both instance and class methods and allows to use the new API on earlier versions of iOS.
https://github.com/dmitrikozlov/MJSocialComposeViewController
Hopefully someone will find it useful.
Comments RSS feed for this page
Add your thoughts, post a comment:
Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.
Let's say that, in my app, the user wants to edit one of his Whizbang objects. In my editing controller, I'll create one of these proxy objects, and point it at the Whizbang he's editing. Then, I bind my interface to the proxy object, not the Whizbang. The editor proxy will then use KVC/KVO to "pull through" values from the Whizbang. If the user makes any changes, those are stored within the proxy. If the user clicks Cancel, those values are blown away. If the user clicks OK, the proxy iterates through its changes and sets the values on the Whizbang.
This eliminates glue code, ties in automatically with the undo manager, etc. It's fantastic.