【问题标题】:Make NSInvocation invoke a specific IMP使 NSInvocation 调用特定的 IMP
【发布时间】:2011-02-19 06:04:56
【问题描述】:

我正在寻找一种方法来使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
}

我已经验证了正确的 self 和正确的 IMP 在 JMP 之前都是正确的,并且 _cmd 参数也正确输入。 (也就是说,我正确地添加了这个方法)。

但是,有些事情正在发生。我有时会发现自己跳到一个方法(通常不是正确的方法),self_cmd 为零。其他时候,我会用 EXC_BAD_ACCESS 在不知名的地方崩溃。想法? (我已经很久没有在汇编中做过任何事情了......)我正在 x86_64 上测试它。

【问题讨论】:

    标签: objective-c nsinvocation imp


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

      事后添加,供参考:

      您可以使用私有 API 来实现。将此类别放在方便的地方:

      @interface NSInvocation (naughty)
      -(void)invokeUsingIMP:(IMP)imp;
      @end
      

      瞧,它完全符合您的预期。我从 Mike Ash 的一篇旧博文中挖掘出这颗宝石。

      像这样的私有 API 技巧非常适合研究或内部代码。请记住从您的应用商店绑定版本中删除它。

      【讨论】:

        【解决方案3】:

        鉴于您已经拥有 IMP,您只需要一种方法来对上述 IMP 的方法调用进行非常原始的转发。并且鉴于您愿意使用类似 NSInvocation 的解决方案,那么您也可以构建一个类似的代理类。

        如果遇到这种情况,我会创建一个简单的代理类,其中包含要调用的 IMP 和目标对象(您需要设置 self 参数)。然后,我会在汇编中编写一个蹦床函数,它接受第一个参数,假设它是代理类的一个实例,获取self,将其填充到持有参数 0 的寄存器中,获取 IMP 和 *JMPs 作为尾声。

        有了 trampoline,您可以将该 trampoline 作为 IMP 添加到您希望转发到特定 IMP 的代理类上的任何选择器......

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

        【讨论】:

        • 我已经用我的蹦床功能更新了我的问题(这不是很有效)。如果您能看一下,我将不胜感激。
        • 你不能用 C 来做......它需要组装,你必须玩一些游戏以获得正确的 IMP。
        【解决方案4】:

        一个未经验证的想法:

        您能否使用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);
        }
        

        【讨论】:

        • +1 一个非常有趣的想法。不幸的是,如果目标 IMP 调用任何其他方法,它们将转到与目标 IMP 相同级别的版本,而不是最远的后代。
        • 更不用说如果你以这样的方式去弄脏类层次结构,那将会对任何并发的东西造成巨大的破坏......(但是,我确实喜欢它!:)
        【解决方案5】:

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

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

        【讨论】:

          【解决方案6】:

          您可能应该看看我们如何在https://github.com/tuenti/TMInstanceMethodSwizzler 中的对象实例上调配特定方法的实现

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

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

          【讨论】:

            【解决方案7】:

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

            但临时方法无法删除。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2016-10-06
              • 2017-05-01
              • 2015-05-23
              • 1970-01-01
              • 2012-07-31
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多