NSInvocation on iOS with gotchas

Dynamic method execution is the strong feature of Cocoa runtime environment. NSInvocation allows users to execute method dynamically using selector and method signature on the fly.

In short you can pass method string to the selector and create invocation out of it on the fly. You do not even need to know method names in advance and sometimes you can execute them dynamically.

As quoted from Apple docs Here

An NSInvocation is an Objective-C message rendered static, that is, it is an action turned into an object. NSInvocation objects are used to store and forward messages between objects and between applications, primarily by NSTimer objects and the distributed objects system.

  • Execution 1 :

    Say you have following method you wish to execute through NSInvocation


- (NSString*)doProcessingWithInput:(NSInteger)input {
    return [NSString stringWithFormat:@"%ld", (long)input];
}


Method is quite simple as it taken NSInteger as input and returns its NSString equivalent.

Let's invoke this method as follows


// Declare input and output variables

NSInteger inputValue = 1511;
NSString* returnValue = @"";

// Get signature for method to be executed 
NSMethodSignature* methodSignature = [self methodSignatureForSelector:@selector(doProcessingWithInput:)];

// Initialize `NSInvocation` object with object of type `NSMethodSignature`
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:methodSignature];

// Set the receiver object or target for invocation
[invocation setTarget:self];

// Set selector to invoke
[invocation setSelector:@selector(doProcessingWithInput:)];

// Set arguments to pass. Advantage of `NSInvocation` over `performSelector` is that you can pass arbitrary number of arguments to it. Make sure to pass arguments by address^
[invocation setArgument:&inputValue atIndex:2];

// We are setting up arguments from index 2 as Cocoa runtime while calling methods implicitly assign two parameters for first 2 indices. First is of type `self` which indicates the receiver of the message and second argument is a `_cmd` which is a message being sent. It also called as a selector

// Invoke the invocation with instance method `invoke`.
[invocation invoke];

// You can also get return value as with getReturnType method. Make sure to pass address of the variable where you wish to store return value.^^
[invocation getReturnValue:&returnValue];
  • Execution 2

    This part is similar to first one except that we will see how you can execute arbitrary method from their name. These method names can be created on the fly depending on the program flow.

Say you have two methods,


- (void)driveCar
- (void)driveBike

These method names could be created using simple NSString manipulation


NSString* vehicleNameFirst = @"Car";
NSString* vehicleNameSecond = @"Bike";

SEL desiredSelector;

if (condition1) {
    desiredSelector = NSSelectorFromString([NSString stringWithFormat:@"drive%@", vehicleNameFirst])  
} else if (condition2) {
    desiredSelector = NSSelectorFromString([NSString stringWithFormat:@"drive%@", vehicleNameSecond])  
}

// Now create method signature using this selector
NSMethodSignature* signature = [NSMethodSignature methodSignatureForSelector:desiredSelector]

// Create NSInvocation object using this method signature
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature];

Now with this invocation you can execute method using technique described Here

^ Though you can pass multiple arguments to any function on runtime through dispatch table and NSInvocation object, I think it's bad practice to pass more than two parameters to method. If you have more parameters to pass, consider creating model objects and assign arguments to pass as a property to it. This is in my opinion a cleaner way to handle this kind of situations

^^ I made a mistake while implementing it for the first time. I called a getReturnValue before invoke call. This caused returnValue parameter to contain nil value. In order to avoid it, make sure to call getReturnValue only after invoke is called on the invocation object.

I hope it will be useful to someone as it did to me sometime back. NSInvocation sounds like difficult concept to grasp. But it's really not. The only difficult problem is to grasp the flavor behind this implementation and use it at appropriate places where a method is being called repeatedly

Gotcha

Make sure to declare return type value with modifier __unsafe_unretained. This is because getReturnValue will try to release value as soon as it returns and variable goes out of scope. This situation is nicely explained in This StackOverflow post.[1]

Update 12/09/2015

[1] There is one more thing to keep us from EXEC_BAD_ACCESS while getting return value from method invoked with NSInvocation. You can call


[invocation retainArguments];

This will retain return values since NSInvocation does not claim their ownership by default. In this case you will not have to declare return value using __unsafe_unretained qualifier.