NSInvocation需要NSMethodSignature

时间:2013-09-05 23:33:18

标签: iphone ios objective-c nsinvocation

如果NSInvocation需要NSMethodSignature,我一直想知道几天。 让我们说我们想写自己的NSInvocation,我的要求就是这样:

  1. 我需要一个选择器SEL
  2. 上调用选择器的目标对象
  3. 参数数组
  4. 然后我会从目标和IMP获取SEL,并将argument作为参数传递。

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

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

2 个答案:

答案 0 :(得分:3)

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

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

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

    • 上面没有提到的是返回类型也可能对调用机制产生很大影响。在x86和ARM上,Objective-C的常见体系结构,当返回类型为struct类型时,调用约定非常不同 - 实际上在所有常规参数之前添加了一个额外的(第一个)参数,是指向应该写入结构结果的空间的指针。这不是常规调用约定,其中结果在寄存器中返回。 (在PowerPC中,我相信double返回类型也是专门处理的。)因此,了解返回类型主要是为了构造和调用NSInvocation

答案 1 :(得分:1)

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

话虽这么说,如果没有方法签名而不修改C语言以支持将数组转换为VARARGS,并且看到objc_msgSend()并且其表兄弟不允许它,则无法创建原始NSInvocation。即使你可以解决这个问题,你也需要计算参数的大小和返回类型(不是太难,但如果你错了,那你就错了大时间),并管理对{的正确调用{1}},这需要熟悉消息发送架构或ffi(无论如何可能会降至__builtin_call())。