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 byNSTimer
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.