如果NSInvocation
需要NSMethodSignature
,我一直想知道几天。
让我们说我们想写自己的NSInvocation,我的要求就是这样:
SEL
然后我会从目标和IMP
获取SEL
,并将argument
作为参数传递。
所以,我的问题是,为什么我们需要NSMethodSignature
来构建和使用NSInvocation
?
注意:我确实知道只有一个SEL
和一个目标,我们没有这个方法的参数和返回类型,但为什么我们要关心args的类型和返回?
答案 0 :(得分:3)
C中的每种类型都有不同的大小。 (即使相同的类型在不同的系统上也可以有不同的大小,但我们现在将忽略它。)int
可以有32位或64位,具体取决于系统。 double
需要64位。 char
表示8位(但可能会作为常规int
传递,具体取决于系统的传递约定)。最后,最重要的是,struct
类型具有不同的大小,具体取决于其中的元素数量和每个大小;没有必要限制它有多大。因此,无论类型如何,都无法以相同的方式传递参数。因此,调用函数如何排列参数,以及被调用函数如何解释其参数,必须依赖于函数的签名。 (您不能拥有与类型无关的“参数数组”;数组元素的大小是多少?)编译正常函数调用时,编译器在编译时知道签名,并可根据调用约定正确排列。但NSInvocation
用于在运行时管理调用。因此,它需要表示方法签名才能工作。
NSInvocation
可以做几件事。这些内容中的每一项都需要了解参数的数量和类型(至少是类型的大小):
NSInvocation
对象并将其传递给-forwardInvocation:
。 NSInvocation
对象包含传递的所有参数的副本,因为它可以在以后存储和调用。因此,运行时需要至少知道参数总共有多大,以便从寄存器和/或堆栈中复制适当数量的数据(取决于调用约定中参数的排列方式)到NSInvocation
对象。NSInvocation
对象时,可以使用-getArgument:atIndex:
查询第i个参数的值。您还可以使用-setArgument:atIndex:
设置/更改第i个参数的值。这要求它知道1)在其数据缓冲区中第i个参数的开始位置;这需要知道以前的参数有多大,以及2)第i个参数有多大,这样它就可以复制适量的数据(如果复制得太少,它会有一个损坏的值;如果它也复制了很多,比方说,当你执行getArgument
时,它可以覆盖你给它的缓冲区;或者当你执行setArgument
时,覆盖其他参数。)-retainArguments
,这会使它保留对象指针类型的所有参数。这要求它区分对象指针类型和其他类型,因此类型信息不仅必须包括大小。NSInvocation
,这会导致它构造并执行对方法的调用。这要求它至少知道从缓冲区复制到寄存器/堆栈中的数据量,以便将所有数据放在函数所期望的位置。这需要至少知道所有参数的组合大小,并且可能还需要知道各个参数的大小,以便可以正确计算寄存器上的参数和堆栈上的参数之间的差异。您可以使用-getReturnValue:
获取通话的返回值;这与上述争论有类似的问题。
struct
类型时,调用约定非常不同 - 实际上在所有常规参数之前添加了一个额外的(第一个)参数,是指向应该写入结构结果的空间的指针。这不是常规调用约定,其中结果在寄存器中返回。 (在PowerPC中,我相信double
返回类型也是专门处理的。)因此,了解返回类型主要是为了构造和调用NSInvocation
。答案 1 :(得分:1)
NSMethodSignature是消息发送和转发机制正常工作以进行调用所必需的。 NSMethodSignature和NSInvocation构建为__builtin_call()
的包装器,它既依赖于体系结构,又对给定函数所需的堆栈空间非常保守。因此,当调用调用时,__builtin_call()
从方法签名中获取所需的所有信息,并且可以通过将调用抛出到转发机制来优雅地失败,因为它知道它也接收到有关堆栈如何的正确信息应该寻找重新调用。
话虽这么说,如果没有方法签名而不修改C语言以支持将数组转换为VARARGS,并且看到objc_msgSend()
并且其表兄弟不允许它,则无法创建原始NSInvocation。即使你可以解决这个问题,你也需要计算参数的大小和返回类型(不是太难,但如果你错了,那你就错了大时间),并管理对{的正确调用{1}},这需要熟悉消息发送架构或ffi(无论如何可能会降至__builtin_call()
)。