简体   繁体   中英

Make NSInvocation invoke a specific IMP

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM