BuySellAds.com

Until Dec 3rd, 44% off all Manning books, including Barcodes with iOS! Promo code: mobicftw
Our DNA is written in Objective-C
Jump

Meta-Calling Multi-Param Methods

Objective-C has several powerful methods of working “Meta”. Besides of handling methods and objects themselves you can also save a method’s signature in a variable, and call it later. That’s a “selector”. Or you could construct an object representing the entire call of a method, including one or multiple parameters and then use this object in place of the actual call.

Why would one do such a complicated thing? Well, there are several pretty useful scenarios where it is useful to know this technique. The main benefit of this method is that you can construct method calls during runtime using information that you only get while your app is running.

Target/Action Reviewed

If you have used any kind of UIControl, for example UIButton, then you have also used the target/action mechanism because this is the way how controls call into your code and perform actions when controls are manipulated. If you want a certain method in your code to be executed whenever a button is pushed, you implement a method with one of 3 possible signatures. A signature being the name or the function plus the names of all parameters and all columns. A method signature is what makes it unique within a class.

UIKit allows three different forms of action selector:

– (void)action
– (void)action:(id)sender
– (void)action:(id)sender forEvent:(UIEvent *)event

It’s up to you to decide how much information you want to get when somebody pushes a button. Either your are just satisfied with the fact that it was pushed, or you also need to know which button it was, or you also want to get fine-grained access to the underlying touch event, which includes the precise pixel where the button was touched.

You know that you can name your methods anything you like, but did you know that you can also change the parameter type to what suits you under the circumstances?

- (IBAction) buttonPushed:(UIButton *)button
{
	switch (button.tag)
	{
		case 1:
			// some code
			break;
		default:
			break;
	}
}

Instead of void signifying that there is no return value we are using IBAction to be able to either programmatically add the event to a button or make the connection in interface builder. But you see that instead of (id)sender I specified a different parameter name and type. Because I know in my code that only buttons will call this method then I can shorten the code necessary and don’t need an extra type cast later.

The selector matching this method is what we can save in a variable of type SEL. Usually you would write it in one line, but for demonstration purposes consider the following:

// a button instance for this demo
UIButton *demoButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
 
SEL myAction = @selector(buttonPushed:);
[demoButton addTarget:self action:myAction forControlEvents:UIControlEventTouchUpInside];

Most of the time Target would be self because you want your method to be within the same viewcontroller or view that the control is in. Action has to be a selector matching a method present within the class instance you are setting as Target. And UIControlEventTouchUpInside is what you usually use for buttons. You CAN specify any selector, those cannot be checked at design-time. But if specify a selector that is not present in the target object you get the dreaded “x does not respond to selector y” exception and your app quits.

If you have a feeling for object oriented programming then it is also self-evident for you why the button itself is the caller of the method, passing a pointer to itself as the first parameter. In summary the target/action mechanism is the predefined way how you can have UI controls call your own methods with zero, one or two parameters when something interesting happens.

Performing Selectors

You now understand that a selector is just a way of keeping the “fingerprint” of a method (it’s signature) in a variable for later use. This later use is what we call “to perform the selector”.

Since the selector only refers to a method LOOKING like it, we also always need to have a class instance which implements it, if we want to execute it. For example, you could have a selector pushAccelerationPedal: but you would also need to be sitting in a car that implements this method (i.e. that HAS an acceleration pedal) in order to perform this action.

Consider the following code, both variants do exactly the same thing:

// assuming there is a class Car
Car *myCar = [[Car alloc] initWithType:@"Ford"];
 
// traditional
[myCar pushAccelerationPedal:1.0];
 
// performing selector
SEL mySelector = @selector(pushAccelerationPedal:);
NSNumber *param = [NSNumber numberWithDouble:1.0];
[myCar performSelector:mySelector withObject:param];

There are variants of performSelector that take zero, one or two objects depending on your need and how you construct the methods that need to be called. Any scalar values (like int, float, double) have to be packaged as objects to be able to pass them. An interesting use for this would be to have the method you are calling run on a background thread using performSelectorInBackground:withObject:.

But there’s a limitation: while you have an option to pass up to two objects via performSelector, you can never pass more than that. The performSelectorInBackground method is limited to just one object for passing.

Invoking NSInvocations

Now we have reached the third and final level: NSInvocation. Let’s recap: for performing selectors you need: a selector, a target and less parameters than some limits. NSInvocation lifts this limit by allowing you to package all ingredients for calling methods with any number of parameters you like into a convenient object.

Let’s look at a real-life example. I am creating a class to wrap a HTTP-based web API which has a method to create a user. The method to create the user sends a HTTP request to my server and waits until there is a response. Because I don’t want my user interface to get stuck I need to perform this in the background and the most elegant way to achieve this is with an operation queue.

I didn’t want to create a new NSOperation subclass just for that. I could have used NSInvocationOperation’s method initWithTarget:selector:object: but there I could have passed only one parameter, but I need two (username and password). This was finally the first good reason for me to search for a simple explanation of NSInvocation.

// there is a class APIClass of which this is a method
// self is the shared instance of this class
// queue is an IVAR
 
#pragma mark Synchronous Calls
- (void)createUserWithUsername:(NSString *)userName password:(NSString *)password
{
	// blocking operation that might take a while
}
 
#pragma mark Async Calls
- (void)asyncCreateUserWithUsername:(NSString *)userName password:(NSString *)password
{
	NSMethodSignature *mySignature = [APIClass instanceMethodSignatureForSelector:@selector(createUserWithUsername:password:)];
	NSInvocation *myInvocation = [NSInvocation invocationWithMethodSignature:mySignature];
	[myInvocation setArgument:&userName atIndex:2];
	[myInvocation setArgument:&password atIndex:3];
	[myInvocation setTarget:self];
	[myInvocation setSelector:@selector(createUserWithUsername:password:)];
 
	NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithInvocation:myInvocation];
	[queue addOperation:op];
	[op release];
}

First we get an NSMethodSignature for the method we want to call by asking our APIClass for it. This holds information on the parameters and their respective types. Then we create an NSInvocation instance with this signature. We set the parameters, beginning at index 2 because 0 and 1 are to internal parameters we don’t want to mess with. Note that we also have to pass the address of our pointers, using the & operator. Finally we set target and selector.

At this stage we have a fully prepped invocation waiting to be invoked. This can either be done by calling [myInvocation invoke] or as I’m showing above by making it into an NSInvocationOperation suitable for putting into an operation queue.

Conclusion

NSInvocations allow you for prepping method calls with a truly arbitrary number of arguments for later calling. One great way to use them is if you want to queue certain methods for background work if they require more than 1 parameter.

Another instance where invocations are frequently used is for supporting Undo. For every editing step you basically construct a method call that undoes it. Then for each undo NSUndoManger takes the newest undo step (which is an NSInvocation) and invokes it.


Categories: Recipes

%d bloggers like this: