为了简化,假设我有类似的功能
void myFunc(id _self, SEL _cmd, id first, ...)
{
}
在那种方法中,我想在_self的超类上调用实现(imp)。 我可以使用以下代码访问IMP:
Class class = object_getClass(_self);
Class superclass = class_getSuperClass(class);
IMP superimp = class_getMethodImplementation(superclass, _cmd);
现在,我该如何调用那个imp?
答案 0 :(得分:3)
只需使用变量参数调用它:
superImp(self, _cmd, argument1, argument2, argument3, etc...)
IMP已经typedef
为
typedef id (*IMP)(id, SEL, ...);
所以你可以用可变参数调用它而没有问题。
答案 1 :(得分:2)
所以,经过大量的工作,实际上我自己想出了这个。
现实是,将varargs传递给C vararg函数是不可能的。为此,您确实需要API来提供v
样式的函数,例如vprintf
。现在,实际上有一个objc_msgSendv
,但它被弃用了。此外,没有objc_msgSendSuperv
。这意味着低级API对我们完全禁止。如果您在编译时知道确切的邮件签名,则此API 仅非常有用。
唯一的另一种选择是使用NSInvocation
机制并构建消息而不是直接调用实现。一个天真的解决方案是相当微不足道的,但正确地做这个有很多警告。
我们需要解决一些问题:
NSInvocation
将我们的参数复制到我们的va_arg
,但是这个宏依赖于我们传递参数的类型,以便它确定要复制和提前的字节大小objc_msgSend
,而不是objc_msgSendSuper
。没有办法直接调用被另一个方法覆盖的方法。void
返回值,但情况并非总是如此。进一步的困难是必须在编译时指定此返回值,因为它是C函数,而不是运行时。我实际上已在以下代码中解决了上述问题:
https://github.com/Lyndir/Pearl/blob/master/Pearl/NSInvocation%2BPearl.h
https://github.com/Lyndir/Pearl/blob/master/Pearl/NSInvocation%2BPearl.m
为解决这些问题,我已完成以下工作:
if
来检查参数大小是否等于已知大小的参数大小,这允许我们执行编译时修复该大小的值va_arg
。这解决了类型参数的问题,其大小等于一系列"支持"大小。在我的实现中,我支持任何类型的字节大小1
(例如char),2
(例如short),4
(例如int),8
(例如.id),16
(例如CGPoint),32
(例如CGRect),48
(例如CGAffineTransform)(参见- (void)setArguments:(va_list)args
)。class_getInstanceMethod
和method_getImplementation
查找隐藏的实现,然后我们可以将该实现安装到新的临时代理方法中使用class_addMethod
在对象的类上。如果我们然后调用代理方法而不是目标方法,运行时将调用我们正在寻找的隐藏实现,实际上与msgSendSuper
一样。但是,这会导致我们使用错误的_cmd
调用实现,因此我们所做的是将隐藏的实现以正确的方法名称分配给顶层。这样做会覆盖我们方法的覆盖代码,所以我们暂时搁置一下,在调用之后,我们将其恢复(参见- (void)invokeWithTarget:(id)target superclass:(Class)type
)。imp_implementationWithBlock
。 (见PearlForwardIMP
)答案 2 :(得分:1)
实际上,我无论如何都无法使用可变数量的参数调用IMP
,但我有另一种解决方案。
使用NSInvocation
,我们可以解决此问题。
void myFunc(id _self, SEL _cmdS, id first, ...) {
Class clazz = object_getClass(_self);
Class superClass = class_getSuperclass(clazz);
NSMethodSignature *signature = [superClass methodSignatureForSelector:_cmdS];
if (!signature) {
return;
}
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:signature];
[inv setSelector:_cmdS];
[inv setTarget:superClass];
va_list args;
va_start(args, first);
id arg = first;
// Arguments 0 and 1 are self and _cmd respectively, automatically set
// by NSInvocation. So start setting arguments from index 2
for (int i = 2; i < signature.numberOfArguments; i++) {
[inv setArgument:&arg atIndex:i];
if (i < signature.numberOfArguments - 1) {
arg = va_arg(args, id);
}
}
va_end(args);
[inv invoke];
}