I'm looking for a way to make an NSInvocation
invoke a specific IMP
. By default, it invokes the "lowest" IMP
it can find (ie, the most-recently-overridden version), but I'm looking for a way to make it invoke an IMP
from higher up in the inheritance chain. The IMP
I want to invoke is determined dynamically, or else I'd be able to use the super
keyword or something like that.
My thought was to use the -forwardInvocation:
mechanism to capture a message (easy and already working) and then alter the IMP
so it goes to a method that is neither the super
implementation nor the furthest descendent's implementation. (hard)
The only thing I've found that comes remotely close is AspectObjectiveC , but that requires libffi, which makes it non-iOS compatible. Ideally I'd like this to be cross platform.
Any ideas?
disclaimer: i'm just experimenting
Trying out @bbum's idea of a trampoline function
So I think I've got things mostly set up; I've got the following trampoline that gets correctly added via class_addMethod()
, and it does get entered:
id dd_trampolineFunction(id self, SEL _cmd, ...) {
IMP imp = [self retrieveTheProperIMP];
self = [self retrieveTheProperSelfObject];
asm(
"jmp %0\n"
:
: "r" (imp)
);
return nil; //to shut up the compiler
}
I've verified that both the proper self and the proper IMP are the right things prior to the JMP, and the _cmd
parameter is also coming in properly. (in other words, I correctly added this method).
However, something is going on. I sometimes find myself jumping to a method (usually not the right one) with a nil self
and _cmd
. Other times I'll just crash in the middle of nowhere with an EXC_BAD_ACCESS. Ideas? (it's been a long time since I've done anything in assembly...) I'm testing this on x86_64.
NSInvocation is just an object representation of a message send. As such, it can't invoke a specific IMP any more than a normal message send could. In order to have an invocation call a specific IMP, you'd either need to write a custom NSInvocation class that goes through the IMP-calling routine or you'd have to write a trampoline that implements the behavior and then create an invocation that represents a message to the trampoline (ie you basically wouldn't be using NSInvocation for much of anything).
Added long after the fact, for reference:
You can do it with private API. Put this category somewhere convenient:
@interface NSInvocation (naughty)
-(void)invokeUsingIMP:(IMP)imp;
@end
and voila, it does exactly what you'd expect. I dug up this gem from one of Mike Ash's old blog posts.
Private API tricks like this are great for research or in-house code. Just remember to excise it from your appstore-bound builds.
An untested idea:
Could you use object_setClass()
to force the selection of the IMP
that you want? That is…
- (void)forwardInvocation:(NSInvocation *)invocation {
id target = [invocation target];
Class targetClass = classWithTheImpIWant();
Class originalClass = objc_setClass(target, targetClass);
[invocation invoke];
objc_setClass(target, originalClass);
}
Given that you already have the IMP, you simply need a way to do a very raw forward of the method call to said IMP. And given that you are willing to use an NSInvocation like solution, then you could also build a similar proxy class.
If I were faced with this, I would create a simple proxying class that contained the IMP to be called and the target object (you'll need to set the self
parameter). Then, I would write a trampoline function in assembly that takes the first argument, assumes it is an instance of the proxying class, grabs the self
, stuffs it into the register holding argument 0, grabs the IMP and *JMPs to it as a tail call.
With trampoline in hand, you would then add that trampoline as an IMP for any selector on the proxying class that you want forwarded to a particular IMP....
To achieve any kind of generic mechanism like this, the key is to avoid anything having to do with rewriting the stack frame . Avoid the C ABI. Avoid moving arguments about.
I think your best choice is to use libffi. Have you seen the port to iOS at https://github.com/landonf/libffi-ios ? I haven't tried the port, but i have successfully invoked IMP with arbitrary arguments on the Mac.
Have a look at JSCocoa https://github.com/parmanoir/jscocoa it includes code to help you prepare a ffi_cif
structure from a Method
and it also contains a version of libffi that should compile on iOS. (Haven't tested either)
You should probably have a look at how we swizzle the implementation of a certain method on an instance of an object in https://github.com/tuenti/TMInstanceMethodSwizzler
Basically, you swizzle the method for all object of a class so when its called it look up in a dictionary whats is the implementation which has to be called for the target object, falling back to the original implementation if not found.
You can also use the private invokeWithImp: method, but this is discouraged if you intent to submit the app to the store.
you could add the IMP to the class using class_addMethod under a new selector and invoke that selector.
the temporary method can't be removed though.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.