使NSInvocation调用特定的IMP

时间:2011-02-19 06:04:57

标签: objective-c nsinvocation imp

我正在寻找一种让NSInvocation调用特定IMP的方法。默认情况下,它会调用它可以找到的“最低”IMP(即最近被覆盖的版本),但我正在寻找一种方法让它从更高的位置调用IMP在继承链中。我要调用的IMP是动态确定的,否则我就可以使用super关键字或类似内容。

我的想法是使用-forwardInvocation:机制来捕获消息(简单且已经正常工作),然后更改IMP,以便它转到不是super实现的方法也没有最远的后代的实施。 (硬)

我发现远程关闭的唯一事情是AspectObjectiveC,但这需要libffi,这使得它与iOS兼容。理想情况下,我希望这是跨平台的。

有什么想法吗?

免责声明:我只是在试验


尝试@bbum关于蹦床功能的想法

所以我觉得我的事情大部分已经建立起来了;我已经通过class_addMethod()正确添加了以下蹦床,并且输入了:

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
}

我已经验证了正确的自我和正确的IMP在JMP之前是正确的事情,并且_cmd参数也正确地进入。 (换句话说,我正确地添加了这种方法)。

然而,事情正在发生。我有时会发现自己跳到一个方法(通常不是正确的方法),只有self_cmd。其他时候我只是在EXC_BAD_ACCESS的中途崩溃。想法? (自从我在汇编中做了很多事以来已经很久了...)我在x86_64上测试它。

7 个答案:

答案 0 :(得分:4)

NSInvocation只是消息发送的对象表示。因此,它不能像普通的消息发送那样调用特定的IMP。为了让调用调用特定的IMP,您需要编写一个通过IMP调用例程的自定义NSInvocation类,或者您必须编写实现该行为的trampoline,然后创建一个表示的调用给蹦床的消息(即你基本上不会在很多事情上使用NSInvocation)。

答案 1 :(得分:3)

未经考验的想法:

您是否可以使用object_setClass()强制选择所需的IMP?那是......

- (void)forwardInvocation:(NSInvocation *)invocation {
    id target = [invocation target];
    Class targetClass = classWithTheImpIWant();
    Class originalClass = objc_setClass(target, targetClass);
    [invocation invoke];
    objc_setClass(target, originalClass);
}

答案 2 :(得分:3)

鉴于您已经拥有了IMP,您只需要一种方法来对IMP表示方法调用。鉴于您愿意使用类似NSInvocation的解决方案,那么您也可以构建类似的代理类。

如果我遇到这个问题,我会创建一个简单的代理类,其中包含要调用的IMP和目标对象(您需要设置self参数)。然后,我会在程序集中编写一个trampoline函数,它接受第一个参数,假设它是代理类的一个实例,抓取self,将它填充到寄存器中保存参数0,抓取IMP和* JMP到它是一个尾巴。

使用trampoline,您可以将该trampoline添加为您要转发给特定IMP的代理类上的任何选择器的IMP ....

要实现这样的任何类型的通用机制,关键是避免与重写堆栈帧有关的任何事情。避免使用C ABI。避免移动争论。

答案 3 :(得分:3)

事后补充说明,供参考:

您可以使用私有API执行此操作。把这个类别放在方便的地方:

@interface NSInvocation (naughty)
-(void)invokeUsingIMP:(IMP)imp;
@end
瞧,瞧,它完全符合你的期望。我从Mike Ash的旧博客文章中挖出了这个宝石。

像这样的私有API技巧非常适合研究或内部代码。只需记住从appstore绑定的构建中删除它。

答案 4 :(得分:2)

我认为你最好的选择是使用libffi。您是否在https://github.com/landonf/libffi-ios看到了iOS端口?我没有尝试过端口,但是我已经在Mac上成功调用了带有任意参数的IMP。

看看JSCocoa https://github.com/parmanoir/jscocoa它包含的代码可以帮助你从ffi_cif准备一个Method结构,它还包含一个应该在iOS上编译的libffi版本。 (还没有测试过)

答案 5 :(得分:0)

你应该看看我们如何在https://github.com/tuenti/TMInstanceMethodSwizzler

中的对象实例上调整某个方法的实现

基本上,你为一个类的所有对象调用方法,所以当它调用它在字典中查找时,必须为目标对象调用实现,如果没有找到,则回退到原始实现。

您也可以使用私有invokeWithImp:方法,但如果您打算将应用程序提交到商店,则不鼓励这样做。

答案 6 :(得分:0)

您可以使用新选择器下的class_addMethod将IMP添加到类中,并调用该选择器。

暂时的方法不能被删除。