6

为了简化,假设我有一个这样的功能

void myFunc(id _self, SEL _cmd, id first, ...)
{

}

在那个方法中,我想在 _self 的超类上调用 implementation(imp)。我可以使用以下代码访问该 IMP:

Class class = object_getClass(_self);
Class superclass = class_getSuperClass(class);
IMP superimp = class_getMethodImplementation(superclass, _cmd);

现在,我该怎么称呼那个 imp ?

4

3 回答 3

3

只需使用可变参数调用它:

superImp(self, _cmd, argument1, argument2, argument3, etc...)

IMP 已经被typedef定义为

typedef id (*IMP)(id, SEL, ...);

因此,您可以毫无问题地使用可变参数调用它。

于 2012-09-03T15:34:18.613 回答
2

所以,经过大量的工作,我自己才真正想通了这一点。

问题

现实情况是不可能将可变参数传递给 C 可变参数函数。为了做到这一点,您确实需要 API 提供一个v-style 函数,例如vprintf. 现在,实际上有一个objc_msgSendv,但它已被弃用。此外,没有objc_msgSendSuperv. 这意味着低级 API 对我们来说是完全禁止的。只有在编译时知道确切的消息签名时,此 API才有用。

唯一的其他选择是使用NSInvocation机器并构建消息,而不是直接调用实现。一个幼稚的解决方案是相当微不足道的,但正确地做到这一点有很多警告。

我们需要解决几个问题:

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

解决方案

我实际上已经在此处可用的代码中解决了上述问题:

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

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

为了解决这些问题,我做了以下工作:

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