Wrapping NSInvocation for fun and profit

In the last post, we saw some ways to avoid the limitations of [NSObject performSelector:withObject], primarily using calls to objc_msgSend and friends, or by getting a pointer to a method's implementation using [NSObject methodForSelector] and calling it directly.

We also saw that while this is handy and quite powerful, if we want to pass anything other than objects around, the syntax starts to get ugly due to all the casting we have to do.

So, we want to be able to write a method that will let us chuck a variable number of arguments of heterogeneous types at an arbitrary selector without paying too much attention to what those types actually are, after all, the implementation must know about them.

Several ways of going about this come to mind, involving various different levels of hackery, but we will apply Larry Wall's three virtues of a programmer : 'laziness, impatience and hubris' and we will pick the most obvious one first. That is, we will cheat.

Much of the difficulty involved in doing this relates to the nature of C's implementation of variadic functions, those functions that can take a variable number of parameters. C's (and therefore Objective C's) way of doing this is via the stdarg macros. Which are tricky. No information about the type or number of arguments is provided, the callee is responsible for knowing this. In essence the passed values are laid out on the stack, and then retrieved using the va_start, va_arg and va_end macros. And that's only about half the story, as we'll see in a later post.

Unfortunately for our purposes, this requires to you to know the type of each argument in advance, which means we can't use it in a suitably generic way without doing non portable, non standard and fairly unsavoury things (of which more later). But we can cheat. We can cheat by ensuring that the we only ever pass objects, just like performSelector does. But because we want to be able to pass things that are not objects, we will wrap them in an NSValue type, which incidentally includes NSNumber for floats and the like. We can then extract the data from the NSValue type and pass a pointer to it to an [NSInvocation setArgument: atIndex:] call.

Here's what a first cut at doing that might look like.

So, a couple of important things to note, starting with the complete lack of error checking, in production code we'd check we had actually retrieved a class, probably by wrapping a [arg Class] message in some exception handling code.

We'd also be wise to compare [arg objcType] with [signature getArgumentTypeAtIndex:] in order to prevent the whole thing blowing up if we pass it something unexpected. These two methods, along with NSGetSizeAndAlignment are where we start to get a taste of the Objective C runtime's facility for type introspection. Using them, we can ascertain both the type of size of the arguments a method expects, something we can't do in plain old C. This suggests at least one further way we might go about this, which we'll look at in a later post.

Performance wise, those multiple malloc calls and double copying of the NSValue data probably isn't great, but we'd need to stick it in a tight loop and profile it to know how much real difference that makes. Premature optimisation and all that. Very probably, we can loose the multiple malloc/free calls, since NSInvocation clearly knows how many bytes it needs to copy.

And lastly, the return value, because we don't, in advance, know what it is going to be, is wrapped in a NSValue using some more of that type introspection we saw earlier applied to the method's return type. Done this way, we'll have to do some introspection of our own on the return value, or simply know what type it is supposed be, in order to get a float, int, char, object, or whatever back out. We could also have passed a pointer to an appropriate return value.

If you're feeling particularly bloody minded - and I am, you can always use a category to monkey patch this onto NSObject.

And here's how you can use the wrapped NSInvocation to pass more than two parameters to a selector of your choice.

Codemonkey, iOS developer, neophyte entrepreneur, frequent hat wearer. Founder of Enigmatic Ape (@EnigmaticApe). Recidivist tea drinker. Sound tennis noob with NEVITC. Now available in Twitter, and ADN

Built With Bootstrap
Powered By Wordpress
Coded By Enigmatic Ape