Callable Objects in Cocoa: NSInvocation

In Java 5 the java.util.concurrent.Callable interface was introduced. This is a generic interface that works like a Runnable, except that it returns a value and can throw checked exceptions.

Using Cocoa, we have a tool that can be used for similar purposes in Objective-C called NSInvocation. We use it transparently in the forwarding implementation of the Strategy Pattern, but don’t really get into the details of how it works. Here we’ll do that and set one up manually, as well as develop a factory-style method for generating them “on” the fly using variable argument lists.

First, let’s build a Math class for testing purposes. It won’t do much: just add two NSNumbers using their double values.

Math Header

1
2
3
4
5
6
7
8
9
@interface Math : NSObject {
 
}
 
+ (Math *)instance;
 
- (NSNumber *) addNSNumber:(NSNumber *)a withNumber:(NSNumber *)b;
 
@end

Math Class File

1
2
3
4
5
6
7
8
9
10
11
12
13
#import "Math.h"
 
 
@implementation Math
 
...
- (NSNumber *) addNSNumber:(NSNumber *)a withNumber:(NSNumber *)b {
	double retval = [a doubleValue] + [b doubleValue];
 
	return [NSNumber numberWithDouble:retval];
}
 
@end

Note that we made this an instance-level method on a Singleton. This is done for the sake of cleanliness and so we can extend the pattern later if we so choose.

Now, for the sake of illustration, we will create a function which adds two numbers and returns the result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#import "Math.h"
 
...
 
- (NSNumber *)run {
	NSNumber *retval;
 
	Math *mathInstance = [Math instance];
 
	NSNumber *arg1 = [NSNumber numberWithDouble:5.0];
	NSNumber *arg2 = [NSNumber numberWithDouble:3.0];
 
	SEL selector = @selector(addNSNumber:withNumber:);
 
	NSMethodSignature *sig = [mathInstance  methodSignatureForSelector:selector];
	NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
 
	[invocation setTarget:mathInstance];
	[invocation setSelector:selector];
	[invocation setArgument:&arg1 atIndex:2];
	[invocation setArgument:&arg2 atIndex:3];
	[invocation invoke];
	[invocation getReturnValue:&retval];
 
	return retval;
}

Let’s break this down step at a time:

Line 6: NSNumber *retval
Here we are creating a pointer to store the return value. We want to do this ahead of time, though truthfully we can do it at any point before we call [NSInvocation getReturnValue:].
Line 8: Math *mathInstance = [Math instance]
Stores a copy of the math instance from the Singleton.
Line 10—11: NSNumber *arg1 =
Declaring the arguments that we are going to use.
Line 13: SEL selector = @selector(addNSNumber:withNumber:)
Creates the selector object for the specific method. Note that it has the type SEL and includes all of the parts of the message (both addNSNumber: and withNumber:.
Line 15: NSMethodSignature *sig = [mathInstance methodSignatureForSelector:selector]
In order to create the NSInvocation we need a copy of the NSMethodSignature.

Note that we could have also used [[mathInstance class] instanceMethodSignatureForSelector:selector] here, but that it wouldn’t work with classes that employ forwarding, such as our previous implementation of the Strategy Pattern.

Line 16: NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]
Creates the actual invocation object. While the invocation object has been created by this step, we still need to configure it.
Line 18: [invocation setTarget:mathInstance];
Sets the Target, or object which will execute the method.
Line 19: [invocation setSelector:selector]
Sets the Selector that we used to create the NSMethodSignature in Line 15.
Line 20-21: [invocation setArgument:&arg1 atIndex:2]
Set the arguments. Notice how the custom arguments start from index 2. This is because indices 0 and 1 represent self and _cmd, respectively. This is similar to Python’s convention of passing self as the first argument to a method, but is hidden from the user.

The other thing to notice, for people who don’t have a strong C background, is how the reference to the argument is used by placing an ampersand (&) in front of it.

This concludes the configuration.

Line 22: [invocation invoke]
Now that the NSInvocation object is configured, we can invoke it. If we hadn’t declared the target earlier, we could use the invokeWithTarget: method.
Line 23: [invocation getReturnValue:&retval]
Get the return value the same way we gave it objects in Lines 20 and 21.

This opens the door for a lot of black magic. It allows us to call methods like [NSObject performSelectorOnMainThread:withObject:waitUntilDone:] with selectors that have more than one argument, or delay execution to happen at a specific time. These are topics we will address more later.

NSInvocation Creation Pattern

It looks like most of this code is just boilerplate, so we should be able to produce a factory-style method to generate these as-needed, taking advantage of the previously discussed variable argument lists:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
+ (NSInvocation *)createInvocationOnTarget:(id)target selector:(SEL)selector {
	return [NGInvoker createInvocationOnTarget:target selector:selector withArguments:nil];
}
 
+ (NSInvocation *)createInvocationOnTarget:(id)target selector:(SEL)selector withArguments:(id)arg1, ... {
	NSMethodSignature *sig = [target methodSignatureForSelector:selector];
	NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
 
	[invocation setTarget:target];
	[invocation setSelector:selector];
 
 
 
	if(arg1) {
		va_list args;
		va_start(args, arg1);
 
		[invocation setArgument:&arg1 atIndex:2];
 
		id obj;
		int ct = 3;
 
		while( obj = va_arg(args, id) ) {
			NSLog(@"%@", obj);
			[invocation setArgument:&obj atIndex:ct];
 
			ct++;
		}
 
		va_end(args);
	}
 
 
	return invocation;
}

Using this, we can then replace everything from Line 15 down with:

1
2
3
4
5
6
NSInvocation *invocation = [NGInvoker createInvocationOnTarget:mathInstance selector:selector withArguments: arg1, arg2, nil]; 
 
[invocation invoke];
[invocation getReturnValue:&retval];
 
return retval;
This entry was posted in Uncategorized. Bookmark the permalink.

Error: The ad management script is not properly configured for this user

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>