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_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
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
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, Google+ and ADN