IMP Caching
IMP caching is a common optimisation strategy in the hot loop of Objective-C code that uses older Objective-C runtime libraries. Instead of using the [object message:argument] syntax that invokes objc_msgSend() or objc_msgLookup(), the process splits message-sending into two phases:
- Use [NSObject methodForSelector:] to retrieve a pointer to the function that implements the method. The return type of this method is
IMP. - Call the function via the pointer found in the step 1, with the receiver as the first argument and the method selector as the second argument.
The goal of IMP caching is to perform the first step once on process launch, instead of using the Objective-C runtime to locate the function dynamically every time the process sends the message. In the Apple Objective-C runtime, IMP caching is less relevant because the runtime library maintains its own implementation cache (and invalidates it correctly when the process swizzles methods on classes, or loads categories that override existing methods).
If you do use IMP caching in code that supports macOS or other Apple platforms, you need to perform extra steps. The header files for the runtime library define IMP as a function that returns void and has no arguments, which is never correct: all Objective-C messages have self and _cmd as two arguments. Because of this, attempts to use the function pointer cause compiler errors:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *greeting = @"Hello, world!";
IMP uppercaseMethod = [greeting methodForSelector:@selector(uppercaseString)];
NSString *shouting = uppercaseMethod(greeting, @selector(uppercaseString));
// Error on above line: Too many arguments to function call, expected 0, have 2
NSLog(@"%@", shouting);
}
return EXIT_SUCCESS;
}
The correct way to use a cached IMP is to cast it to a function pointer with the correct signature:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *greeting = @"Hello, world!";
IMP uppercaseMethod = [greeting methodForSelector:@selector(uppercaseString)];
NSString *shouting = ((id (*)(id, SEL))uppercaseMethod)(greeting, @selector(uppercaseString));
// No error.
NSLog(@"%@", shouting);
}
return EXIT_SUCCESS;
}
Additionally, if your code supports AArch64 including Apple Silicon, be aware that the ABI is different for variadic arguments (which callers pass on the stack) and non-variadic arguments (which callers pass in the register file). Don’t try to define a “catch-all” function type like typedef id (*myIMP)(id, SEL, ...) to cache IMPs as this will lead to your methods receiving incorrect values at runtime, which could potentially cause crashes or security problems.
