Movatterモバイル変換


[0]ホーム

URL:


mikeash.com: just this guy, you know?
Home
Book
The Complete Friday Q&A, advanced topics in Mac OS X and iOS programming.
Blog
GitHub
My GitHub page, containing various open-source libraries for Mac and iOS development, and some miscellaneous projects
Glider Flying
HD Ridge Running Video
List of Landouts
Day in the Life
Skyline Soaring Club
Soaring Society of America
Getting Answers
Ten simple points to follow to get good answers on IRC, mailing lists, and other places
Miscellaneous Pages
Miscellaneous old, rarely-updated content
mike@mikeash.com
E-mail me

Posted at 2009-03-27 16:50 |RSS feed (Full text feed) |Blog Index
Next article:Friday Q&A 2009-04-03: On Hold
Previous article:Friday Q&A 2009-03-20: Objective-C Messaging
Tags:fridayqnaobjectivec
Friday Q&A 2009-03-27: Objective-C Message Forwarding
byMike Ash  
This article is also available inChinese (translation by neoman).

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 aselector is or what the difference is between amethod and amessage, 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.

Right about now, you're probably wondering, "Why is it calledforwarding?" There doesn't seem to be much of a link between taking arbitrary actions in response to unknown messages, and "forwarding". The reason for this is because this technique was mainly intended to allow objects to let other objects handle the message for them, thus "forwarding".

What Happens
What happens when you do[foo bar] andfoo doesn't implement abar method? When itdoes 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:

  1. Lazy method resolution. This is done by sendingresolveInstanceMethod: (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.
  2. Fast forwarding path. This is done by sendingforwardingTargetForSelector: 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.
  3. Normal forwarding path. First the runtime will sendmethodSignatureForSelector: 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, orIMP, 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 inNSObject.h, is in theLeopard 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;}
This will cause any unknown message to be sent to_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 anNSInvocation 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 theNSInvocation it needs anNSMethodSignature, so it requests one using-methodSignatureForSelector:. This is required due to Objective-C's C heritage. In order to bundle the arguments up in theNSInvocation, 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 yourforwardInvocation: 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 toNSArray:

@implementationNSArray(ForwardingIteration)-(NSMethodSignature*)methodSignatureForSelector:(SEL)sel{NSMethodSignature*sig=[supermethodSignatureForSelector:sel];if(!sig){for(idobjinself)if((sig=[objmethodSignatureForSelector:sel]))break;}returnsig;}-(void)forwardInvocation:(NSInvocation*)inv{for(idobjinself)[invinvokeWithTarget:obj];}@end
Then you can write code like this:
[(NSWindow*)windowsArraysetHidesOnDeactivate:YES];
I don't recommend writing code like this. The trouble is that forwarding won't catch any methods already implemented by NSArray, so you'll end up being able to capture some but not others. A much better approach is to write a trampoline class by subclassingNSProxy.

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 useNSProxy 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 toNSArray that returns a new instance of the proxy, like so:

@implementationNSArray(ForwardingIteration)-(id)do{return[MyArrayProxyproxyWithArray:self];}@end
Then you'd use it like this:
[[windowsArraydo]setHidesOnDeactivate:YES];
This whole area of writing trampolines to capture messages and have them do interesting things has been well explored and has been given the nameHigher-Order Messaging. I won't go into more detail about it in this post, but there's a lot of neat stuff out there.

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:

intx=[converterconvert_42];
This is obviously not very useful, but you could certainly do it. More useful variants of this technique are possible.

The trouble is that the compiler doesn't know about anyconvert_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 returnsid. The fix to this is simple, just declare one somewhere:

@interfaceNSObject(Conversion)-(int)convert_42;-(int)convert_29;@end
Again, this obviously isn't very useful to do, but in cases where you have a more practical forwarding situation, this can help you make peace with the compiler. For example, if you use forwarding to fake multiple inheritence, use a category to declare all of the methods of the other class as applying to the multiply-inheriting class as well. That way the compiler knows that it has both sets of methods. One set gets handled by forwarding, but that doesn't matter to the compiler.

Conclusion
Message forwarding is a powerful technique that greatly multiplies the expressiveness of Objective-C. Cocoa uses it for things likeNSUndoManager 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 ore-mail it directly. I will use your name unless you ask me not to.

Did you enjoy this article? I'm selling whole books full of them! Volumes II and III are now out! They're available as ePub, PDF, print, and on iBooks and Kindle.Click here for more information.

Comments:

Steveat2009-03-28 01:21:07:
Message forwarding is a great feature of Objective-C, and lets you do some bizarre but useful stuff. One way I've used runtime proxies is in building an object editor with OK/cancel.

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.
Sijmenat2009-03-28 01:52:26:
Steve, that's a great idea.

Anyone else tried something similar? I'm interested in any experiences with that approach.

Great article btw.
Matt Gallagherat2009-03-28 06:35:54:
Mike, your posts continue to be great. I would never have seen forwardingTargetForSelector: if you hadn't pointed it out.

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
Vasiat2009-03-28 06:45:39:
Is there any portable way to generate NSMethodSignatures? You can get one using a type encoding[1], but I'm under the impression Apple doesn't guarantee that the type encoding will always stay the same.

[1]http://developer.apple.com/DOCUMENTATION/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
mikeashat2009-03-29 08:25:42:
The best way to generate an NSMethodSignature is to get one with an NSObject method. Generally you will know what kind of signature you want when you write the code, so you can just write a little stub method (if you don't have a "real" one available) and then ask the object or class for the method signature for that method.

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.
Pitiphong Phongpattranontat2011-06-06 10:44:58:
What method do you think apple use in the @dynamic properties declaration in the CoreData framework?
mikeashat2011-06-11 20:38:07:
To my knowledge, @dynamic uses lazy method resolution with a great deal of internal trickery which allows it to bypass the slower but easier-to-deal-with normal forwarding path.
Peter N Lewisat2011-09-28 02:08:01:
Thanks for a great article Mike! forwardingTargetForSelector is very useful for the normal forwarding case, and I'm using it now to insert a delegate object to handle normal behaviour and pass on unused delegate messages to a user supplied delegate.

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.
Denisat2012-03-01 19:57:48:
Hi!

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.

mikeashat2012-03-01 20:34:57:
I believe you can simply implement the forwarding methods as class methods and they will work to intercept messages sent to the class.
Denisat2012-03-02 04:12:55:
Thank you, Mike!
I've actually did it when read about meta-class, just after i asked this question. Objective-C doesn't stop to surprise me!
Dmitri Kozlovat2013-04-18 08:28:22:
Mike, that is a very helpful post.
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.

Name:
The Answer to the Ultimate Question of Life, the Universe, and Everything?
Comment:
Formatting:<i> <b> <blockquote> <code>.
NOTE: Due to an increase in spam, URLs are forbidden! Please provide search terms or fragment your URLs so they don't look like URLs.
Code syntax highlighting thanks toPygments.
Hosted atDigitalOcean.

[8]ページ先頭

©2009-2025 Movatter.jp