Making NSInvocations

NSInvocation is a pretty useful class. It is Objective-C's version of a function object. It encapsulates a method call allowing you to pass it around, modify it, and call it later.

The problem with NSInvocation is how hard it is to initialize. Here is the simplest NSInvocation construction I can think of:

SEL sel = @selector(myMethod);  
NSInvocation* inv = [NSInvocation invocationWithMethodSignature:  
    [self methodSignatureForSelector:sel]];
[inv setTarget:self];
[inv setSelector:sel];

Clearly this is unwieldy at best, especially if you just want to make a method call.

Even worse is trying to create an invocation when all you have is a protocol. I came across this problem when trying to implement the observer pattern in Objective-C. I wanted to call all observers in a loop, without having to copy and paste the loop code for every observer method I had. And I still wanted to support methods that take non-object parameters, which means performSelector: was not an option.

Here is what I came up with. I added three new factory methods to the NSInvocation class with a category:

@interface NSInvocation (Constructors)
+ (id)invocationWithTarget:(NSObject*)targetObject
                  selector:(SEL)selector;
+ (id)invocationWithClass:(Class)targetClass
                 selector:(SEL)selector;
+ (id)invocationWithProtocol:(Protocol*)targetProtocol
                    selector:(SEL)selector;
@end

So that initializing an invocation becomes a one-liner:

NSInvocation* inv = [NSInvocation  
    invocationWithTarget:self
                selector:@selector(myMethod)];

Even if all you have is a protocol:

NSInvocation* inv = [NSInvocation  
    invocationWithProtocol:@protocol(MyProtocol)
                                                selector:@selector(myMethod)];
// ...Later:
[inv setTarget:someTarget];
[inv invoke];

The first factory method is easy to implement, it's what you would normally write to create an invocation:

+ (id)invocationWithTarget:(NSObject*)targetObject
                  selector:(SEL)selector {
    NSMethodSignature* sig = [target
        methodSignatureForSelector:selector];
    NSInvocation* inv = [NSInvocation
        invocationWithMethodSignature:sig];
    [inv setTarget:target];
    [inv setSelector:selector];
    return inv;
}

The next two factory methods are a bit more involved, they require using little-known Objective-C functions. First we'll need to use class_getInstanceMethod which returns a method description structure given a class and a selector. Then we call method_getDescription to get a method description stucture. From that we can construct an NSMethodSignature, which we use to construct the NSInvocation. The trick is using the signatureWithObjCTypes: factory method of the NSMethodSignature class with the types element of the description returned by method_getDescription:

+ (id)invocationWithClass:(Class)targetClass
                 selector:(SEL)selector {
    Method method =
        class_getInstanceMethod(targetClass, selector);
    struct objc_method_description* desc =
        method_getDescription(method);
    if (desc == NULL || desc->name == NULL)
        return nil;

    NSMethodSignature* sig = [NSMethodSignature
        signatureWithObjCTypes:desc->types];
    NSInvocation* inv = [NSInvocation
        invocationWithMethodSignature:sig];
    [inv setSelector:selector];
    return inv;
}

The protocol factory method is somewhat similar to the previous one but we only need one function call, protocol_getMethodDescription, which returns the description right away. The complication here is that you need to know if the method is static or not and if the method is required or optional. This seems kind of silly because the language should be able to determine that from the protocol definition. But anyway, lets assume the method will not be static. We first try assuming it is a required method and if that fails we try optional. Here is the code:

+ (id)invocationWithProtocol:(Protocol*)targetProtocol
                    selector:(SEL)selector {
    struct objc_method_description desc;
    BOOL required = YES;
    desc = protocol_getMethodDescription(targetProtocol,
                                         selector,
                                         required,
                                         YES);
    if (desc.name == NULL) {
        required = NO;
        desc = protocol_getMethodDescription(targetProtocol,
                              selector,
                              required,
                              YES);
    }
    if (desc.name == NULL)
        return nil;

    NSMethodSignature* sig = [NSMethodSignature
        signatureWithObjCTypes:desc.types];
    NSInvocation* inv = [NSInvocation
        invocationWithMethodSignature:sig];
    [inv setSelector:selector];
    return inv;
}

Creating an NSInvocation is so cumbersome as to be discouraging. At least once in the past, I have changed the design of a piece of code where I could use function objects just because I didn't want add all the ugly code needed to create them. But with the code here, in a single line, you can:

  • Create an NSInvocation from an object and a selector
  • Create an NSInvocation from a class and a selector
  • Create and NSInvocation from a protocol and a selector

Hopefully, like me, now you will use more of them. On the next article we'll put them to good use, stay tuned.

Here is a different approach to the same problem by Matt Gallagher: Construct an NSInvocation for any message, just by sending. No support for constructing from a class or a protocol, though.

The source code for this article, including unit tests, is available at GitHub.

comments powered by Disqus