我正在寻找一种让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上测试它。
答案 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添加到类中,并调用该选择器。
暂时的方法不能被删除。