如何使用可变数量的参数调用实现?

时间:2012-09-03 15:20:44

标签: objective-c objective-c-runtime

为了简化,假设我有类似的功能

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?

3 个答案:

答案 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机制并构建消息而不是直接调用实现。一个天真的解决方案是相当微不足道的,但正确地做这个有很多警告。

我们需要解决一些问题:

  1. 传递不同类型的参数(读取大小)。我们使用NSInvocation将我们的参数复制到我们的va_arg,但是这个宏依赖于我们传递参数的类型,以便它确定要复制和提前的字节大小
  2. 调用被另一个覆盖(读取隐藏)的实现。此外,由于我们现在只是发送消息,因此无法选择类的精确实现。实质上,我们只能objc_msgSend,而不是objc_msgSendSuper。没有办法直接调用被另一个方法覆盖的方法。
  3. 检索返回值。我们的新IMP函数需要具有与我们要替换的函数的返回类型匹配的返回类型。 OP使用void返回值,但情况并非总是如此。进一步的困难是必须在编译时指定此返回值,因为它是C函数,而不是运行时。
  4. 解决方案

    我实际上已在以下代码中解决了上述问题:

    https://github.com/Lyndir/Pearl/blob/master/Pearl/NSInvocation%2BPearl.h

    https://github.com/Lyndir/Pearl/blob/master/Pearl/NSInvocation%2BPearl.m

    为解决这些问题,我已完成以下工作:

    1. 对于每个参数,我们查看方法的签名,该签名对参数类型进行编码。我们确定参数类型的字节大小,并执行一系列if来检查参数大小是否等于已知大小的参数大小,这允许我们执行编译时修复该大小的值va_arg。这解决了类型参数的问题,其大小等于一系列"支持"大小。在我的实现中,我支持任何类型的字节大小1(例如char),2(例如short),4(例如int),8(例如.id),16(例如CGPoint),32(例如CGRect),48(例如CGAffineTransform)(参见- (void)setArguments:(va_list)args)。
    2. 要调用被另一个方法覆盖的方法的实现,我们可以使用class_getInstanceMethodmethod_getImplementation查找隐藏的实现,然后我们可以将该实现安装到新的临时代理方法中使用class_addMethod在对象的类上。如果我们然后调用代理方法而不是目标方法,运行时将调用我们正在寻找的隐藏实现,实际上与msgSendSuper一样。但是,这会导致我们使用错误的_cmd调用实现,因此我们所做的是将隐藏的实现以正确的方法名称分配给顶层。这样做会覆盖我们方法的覆盖代码,所以我们暂时搁置一下,在调用之后,我们将其恢复(参见- (void)invokeWithTarget:(id)target superclass:(Class)type)。
    3. 为了创建一个具有适合我们任意IMP的返回值的返回类型的C函数,我们再次查看方法签名,这次,计算出我们的返回值值。我们执行另一系列条件来检查已知大小的大小,并且对于每个已知大小,我们创建一个具有该大小硬编码的C函数作为函数的返回值。我已经为尺寸的返回值创建了支持为了实现这一点,我们使用了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];
}