0

这几天我一直在想是否NSInvocation需要NSMethodSignature. 假设我们想编写自己的 NSInvocation,我的要求是这样的:

  1. 我需要一个选择器SEL
  2. 调用选择器的目标对象
  3. 参数数组

然后我会IMP从目标和 中取出SEL,并传递argumentas 参数。

所以,我的问题是,为什么我们需要一个NSMethodSignature来构造和使用一个NSInvocation

注意:我知道只有 aSEL和一个目标,我们没有这个方法的参数和返回类型,但是我们为什么要关心 args 和返回的类型呢?

4

2 回答 2

3

C 中的每种类型都有不同的大小。(即使相同的类型在不同的系统上也可以有不同的大小,但我们现在将忽略这一点。)int可以有 32 位或 64 位,具体取决于系统。double占用 64 位。char表示 8 位(但可能作为常规传递int根据系统的传递约定作为常规传递)。最后也是最重要的,struct类型有不同的大小,取决于其中有多少元素以及它们的每个大小;它可以有多大是没有限制的。因此,无论类型如何,都不可能以相同的方式传递参数。因此,调用函数如何排列参数,以及被调用函数如何解释其参数,必须取决于函数的签名。(你不能有一个与类型无关的“参数数组”;数组元素的大小是多少?)当编译正常的函数调用时,编译器在编译时知道签名,并且可以根据调用约定正确排列它。但是NSInvocation用于在运行时管理调用。因此,它需要方法签名的表示才能工作。

有几件事NSInvocation可以做。这些事情中的每一个都需要了解参数的数量和类型(至少是类型的大小):

  1. 当消息被发送到没有方法的对象时,运行时会构造一个NSInvocation对象并将其传递给-forwardInvocation:. 该NSInvocation对象包含所有传递的参数的副本,因为它可以在以后存储和调用。因此,运行时至少需要知道参数总共有多大,以便将正确数量的数据从寄存器和/或堆栈(取决于调用约定中参数的排列方式)复制到NSInvocation目的。
  2. 当你有一个NSInvocation对象时,你可以查询第 i 个参数的值,使用-getArgument:atIndex:. 您还可以设置/更改第 i 个参数的值,使用-setArgument:atIndex:. 这要求它知道 1) 第 i 个参数在其数据缓冲区中的何处开始;这需要知道前面的参数有多大,以及 2)第 i 个参数有多大,以便它可以复制正确数量的数据(如果它复制的太少,它将具有损坏的值;如果它复制太多很多,比如说,当你这样做时getArgument,它可以覆盖你给它的缓冲区;或者当你这样做时setArgument,覆盖其他参数)。
  3. 你可以让它做-retainArguments,这会导致它保留对象指针类型的所有参数。这要求它区分对象指针类型和其他类型,因此类型信息必须不仅包括大小。
  4. 您可以调用NSInvocation,这会导致它构造并执行对方法的调用。这要求它至少知道要从其缓冲区复制多少数据到寄存器/堆栈中,以将所有数据放置在函数期望的位置。这需要至少知道所有参数的组合大小,并且可能还需要知道各个参数的大小,以便正确计算出寄存器上的参数和堆栈上的参数之间的划分。
  5. -getReturnValue:您可以使用;获取调用的返回值 这与上述论点的获取有类似的问题。

    • 上面没有提到的一点是,返回类型也可能对调用机制有很大的影响。在 x86 和 ARM(Objective-C 的通用架构)上,当返回类型是struct类型时,调用约定非常不同——实际上在所有常规参数之前添加了一个附加(第一个)参数,它是指向应该写入结构结果的空间。这不是在寄存器中返回结果的常规调用约定。(在 PowerPC 中,我相信double返回类型也被特殊对待。)所以知道返回类型本质上是为了构造和调用NSInvocation.
于 2013-09-06T08:37:01.363 回答
1

消息发送和转发机制需要 NSMethodSignature 才能正常调用。NSMethodSignature 和 NSInvocation 被构建为一个包装器__builtin_call(),它既依赖于架构,又对给定函数所需的堆栈空间极为保守。因此,当调用被调用时,__builtin_call()从方法签名中获取它需要的所有信息,并且可以通过将调用抛出给转发机制来优雅地失败,因为它知道它也接收到关于堆栈应该如何查找重新的正确信息。调用。

话虽这么说,如果不修改 C 语言以支持将数组转换为 VARARGS 看 asobjc_msgSend()并且它的表亲不允许这样做,就不能在没有方法签名的情况下制作原始 NSInvocation 。即使你可以解决这个问题,你也需要计算参数的大小和返回类型(不是太难,但如果你错了,你就错了),并管理对__builtin_call(),这需要对消息发送架构或 ffi 有深入的了解(__builtin_call()无论如何都可能下降)。

于 2013-09-06T00:56:29.263 回答