More of a programming nerd than is strictly healthy. See also {nevyn.nu, thirdcog.eu, twitter}

Projects

nevyn's blog

One-line closures in ObjC without blocks; or, NSInvocation fun

Objective-C is wonderfully dynamic. One of my favorite wonders is the invocation grabber. I never paid them much attention at first, only using them when I explicitly needed an NSInvocation for some API. Then, I started working at Spotify and found a certain header file; suddenly, I saw the light.

If you know what that an invocation grabber is, you can just skip down to the code. For the rest, I’ll elaborate.
Shortcut: Just gimme the juicy bits, I’m impatient!

NSInvocations

An NSInvocation is a standard Foundation class that wraps four pieces of information all related to calling a method:

Or, in the form you are used to see these parts in code:

[target partOfSelector:argument1 secondPartOfSelector:argument2]

Normally, these are kind of tricky to construct, particularly the method signature. However, Objective-C’s message forwarding mechanism practically gives them to us for free!

Message forwarding?

(Short clarification detour: In Objective-C, you almost never call methods directly; instead, you send messages to objects, and the receiver almost always responds to the message by calling the method corresponding to that message on itself. This is why the terms ‘method calling’ and ‘messaging’ are often used interchangeably in ObjC, but they are not the same thing.)

When you call a method on an instance whose class does not define that method, you don’t get a compiler error as in C++. When you then run your code, the runtime assumes that you’re trying to do message forwarding. Message forwarding means that the receiver of the message will not itself handle the message, but is only a proxy for some other object. One common example is Distributed Object, where you’re sending messages (calling methods) on objects in other applications, or even other applications on other computers.

Writing an invocation grabber

This is where the invocation grabber comes in. We can create a class that does nothing but listen for message forwarding requests, and when it receives one, asks the target what it would respond to such a request. Given this answer, an NSInvocation can be created which matches the invocation of the message we sent to it.

Consider the following example:

We define a MyClass, create an instance of it, then create an invocationGrabber with that new MyClass as its target, and then try to call -[MyClass areTheNewViewersGoneYet:] on the grabber. Finally, we fetch the result of the invocation grabbing.

Note that line 9 does not call areTheNewViewersGoneYet: on your new MyClass. Instead, the grabber intercepts that call and gives us an NSInvocation that would have called that line just as we wrote it, but some time in the future instead of now. This is like a one-line closure, as the code, state and variables you send to the grabber are saved until later, just waiting to be executed.

Our invocation grabber does not implement a method with the selector areTheNewViewersGoneYet:. Given just the invocation grabber and that selector, the runtime would have no idea how to call it. Does it return a bool, in which case it will need to reserve four bytes for the return value? Or maybe a CGRect, in which case 16 bytes are needed.

So to figure this (and related things) out, the runtime will begin by asking for the method signature for the selector being sent with the message with -[NSObject methodSignatureForSelector:(SEL)]. Our SPInvocationGrabber itself has no idea, so we implement that method to ask the target:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)inSelector {
	return [_target methodSignatureForSelector:inSelector];
}

With the signature in hand, the runtime will now give you an NSInvocation and ask you to take care of invocation with -[NSObject forwardInvocation:]. In our case, we’ll just stash away the invocation for later use. In the example above, we created a yellowDuck and sent it as an argument to areTheNewViewersGoneYet:, and if we delay our execution of the NSInvocation, that duck might be released with the nearest NSAutoreleasePool. Thus, in forwardInvocation, we ask the NSInvocation to retain all its arguments. Don’t worry, when we release the NSInvocation, it will release those arguments too.

- (void)forwardInvocation:(NSInvocation *)anInvocation {
	[anInvocation retainArguments];
	anInvocation.target = _target;
	self.invocation = anInvocation;
}

That’s basically all there is to it! Some memory management and accessor methods, and we have our own invocation grabber.

So what’s all the fuss about?

Let’s start out with some code.

That’s the header. It has a few categories on NSObject, which is where it’s at. (A category on NSObject means that we are adding methods on almost all other classes; these three methods can be called on any instance of a class inheriting from NSObject.)

-[NSObject grab] gives you an invocation grabber for the receiver, so that instead of [[[SPInvocationGrabber alloc] initWithObject:foo] autorelease] we can just say [foo grab]. It doesn’t sound like much, but it goes a long way toward taking the hassle out of doing magic.

-[NSObject invokeAfter:] will give you a proxy object that, instead of performing the method you call on it right away, instead invokes it later. Again, doesn’t sound like much, but man, -[NSObject performSelector:withObject:afterDelay:] is a mouthful, and it doesn’t work with non-object arguments or calls that take more than two arguments. Suddenly delayed execution is simple.

-[NSObject nextRunloop] is the same as invokeAfter: with a zero second delay. This is the common idiom for having something executed the next runloop (or at least as soon as possible after the current runloop).

I’ll show you what it can do with some examples:

A: Yeah yeah, we could have set an animation delegate, and animation did finish selector, and from there called removeFromSuperview. That’s at least six lines of code. Now we did it with one: [[flash invokeAfter:duration+0.1] removeFromSuperview].

B: Some things work better if you call them very soon instead of right away. Sometimes it’s an animation that needs to have started before we can begin modifying it. Other times it’s a timing issue and creating a delegate or notification is a lot of hassle for something that will just work one runloop from now.

C: Hey, who thought animations could be so simple? Don’t worry if the view controller that owns that table goes away before the animation finished: the invocation grabber retains both the table view and the arguments, so for 450 milliseconds, those objects *will* remain.

There’s more magic, but I’ll go through that after the implementation:

First, stack trace saving. You have no idea how useful this is. If you do the old -[NSObject performSelector:withObject:afterDelay:] trick, you’ll note that sometimes you crash. You’ll also note that below the method you just delayed execution of, you have no useful backtrace. Fret not, for execinfo.h is here to save your day! When an SPInvocationGrabber is created, the names of the symbols on the stack are saved away with -[saveBacktrace]. Then, if you use the grabber’s invoke instead of -[NSInvocation invoke], the invocation will be wrapped in a try-catch, and in the event of an exception, the old backtrace will be printed, showing you the culprit that scheduled this broken future invocation.

As for those convenient categories on NSObject, they are ridiculously simple. All -[invokeAfter:] does is to create a grabber, schedule it for execution later with an NSTimer (before it has grabbed anything, even!), and then returns it, ready to grab something for later execution.

I’ve tried to create -[NSObject invokeOnMainThread] and -[NSObject invokeOnBackgroundThread], but those are trickier, because you can’t just schedule the invocation and then return the grabber, as -[NSObject performSelectorOnMainThread:withObject:waitUntilDone:] would perform -[invoke] immediately, giving us no time to do the invocation grabbing, as with -[invokeAfter:]. It’s easy to do on your own though, even without a convenience method:

  id grabber = [foo grab];
  [grabber doSomethingFancyThatMustBeDoneOnMainThread:object someArg:wohoo];
  [grabber performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:NO];

Enjoy, and do post your own invocation tricks in the comments and reblogs.

Update 20100828: As you can see from the gist, I’ve updated it with background and main thread invocation (example usage below); thanks to Adam Crume in the comments for the idea and half of the code.

  1. nevyn posted this