【问题标题】:NSInvocation needing NSMethodSignatureNSInvocation 需要 NSMethodSignature
【发布时间】:2013-09-06 18:13:21
【问题描述】:

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

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

然后我会从目标中取出IMPSEL,并将argument 作为参数传递。

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

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

【问题讨论】:

    标签: iphone ios objective-c nsinvocation


    【解决方案1】:

    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

    【讨论】:

    • 所以你说的是内部 NSInvocation 不使用 objc_msgSend,它使用直接调用 fptr 并将 args 复制到寄存器等......
    • 但是为什么我们将SEL、目标和NSMethodSignature传递给它,当我们知道我们可以通过[target methodSignatureForSelector:SEL]获得签名时,我们可以只传递SEL和目标并且仍然在运行时获得方法 sig 。我在这里错过了什么?
    • @OmarAbdelhafith:不,它确实使用了objc_msgSend()。但是为了正确使用objc_msgSend(),您还必须知道签名——您必须假设objc_msgSend 具有您正在调用的函数的函数指针的类型。因为objc_msgSend() 只是一个蹦床,它用被调用的函数替换自己,使寄存器和堆栈中的所有参数保持不变。
    • @OmarAbdelhafith:当运行时通过调用创建NSInvocation 时,它确实使用了-methodSignatureForSelector:。当您自己创建NSInvocation 时,您不需要立即指定目标。您可以在设置其他参数后设置目标。所以一开始就需要签名。我猜你可能会说它应该需要目标来创建它。我正在尝试考虑是否存在您想要使用与[target methodSignatureForSelector:SEL] 返回的签名不同的签名但我现在想不出的情况。
    • 我想我现在能想到一个,它当目标没有选择器的实现时,转发机制将需要一个签名来创建一个 NSInvocation 来调用另一个目标,你认为这个适用吗?
    【解决方案2】:

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

    话虽如此,如果不修改 C 语言以支持将数组转换为 VARARGS (视为objc_msgSend()),则无法在没有方法签名的情况下创建原始 NSInvocation,其表亲不允许这样做。即使你能解决这个问题,你也需要计算参数的大小和返回类型(不是太难,但如果你错了,那你就大错特错了),并管理对 @ 的正确调用987654324@,这需要对消息发送架构或 ffi 有深入的了解(无论如何可能会下降到 __builtin_call())。

    【讨论】:

    • 但是为什么我们将 SEL、目标和 NSMethodSignature 传递给它,当我们知道我们可以通过执行 [target methodSignatureForSelector:SEL] 来获得签名时,我们可以只传递 SEL 和目标并且仍然在运行时获取方法 sig 。我在这里错过了什么?
    猜你喜欢
    • 1970-01-01
    • 2012-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多