为什么 Objective-C 编译器需要知道方法签名?

Why does the Objective-C compiler need to know method signatures?

为什么 Objective-C 编译器需要在编译时知道将在对象上调用的方法的签名,而它可以将其推迟到运行时(即动态绑定)?例如,如果我写 [foo someMethod],为什么编译器需要知道 someMethod 的签名?

因为调用约定最少(用ARC,原因比较多,但调用约定一直是个问题)

您可能已经被告知 [foo someMethod] 被转换为函数调用:

objc_msgSend(foo, @selector(someMethod))

然而,这不完全是真的。它可能会转换为许多不同的函数调用,具体取决于它 returns 的内容(无论您是否使用结果,返回的内容都很重要)。例如,如果它 returns 一个对象或整数,它将使用 objc_msgSend,但如果它 returns 一个结构(在 ARM 和 Intel 上)它将使用 objc_msgSend_stret,如果它 returns 是 Intel 上的浮点数(但我相信不是 ARM),它将使用 objc_msgSend_fpret。这都是因为在不同的处理器上,调用约定(如何设置堆栈和寄存器,以及结果的存储位置)因结果而异。

参数是什么以及有多少也很重要(可以从 ObjC 方法名称推断出数量,除非它们是可变参数……对,您也必须处理可变参数)。在某些处理器上,前几个参数可能放在寄存器中,而后面的参数可能放在堆栈中。如果您的函数采用可变参数,那么调用约定可能仍然不同。所有这些都必须知道才能编译函数调用。

ObjC 可以作为一个更纯粹的对象模型来实现,以避免所有这些(就像其他更动态的语言所做的那样),但这将以性能为代价(space 和时间)。考虑到动态调度级别,ObjC 可以使方法调用出奇地便宜,并且可以轻松地使用纯 C 机器类型,但这样做的代价是我们必须让编译器了解我们的方法签名的更多细节。

顺便说一句,这会(并且经常会)导致非常可怕的错误。如果您有两种方法:

- (MyPointObject *)point;

- (CGPoint)point;

也许它们在完全不同的文件中定义为不同 类 上的方法。但是,如果编译器选择了错误的定义(例如当您向 id 发送消息时),那么您从 -point 返回的结果可能完全是垃圾。这是一个非常非常难以弄清楚何时发生的错误(我已经发生过)。

要了解更多背景知识,您可以欣赏 Greg Parker 的 article explaining objc_msgSend_stret and objc_msgSend_fpret. Mike Ash also has an excellent introduction to this topic. And if you want to go deep down this rabbit hole, you can see bbum's instruction-by-instruction investigation of objc_msgSend。它现在已经过时了,ARC 之前,并且只涵盖 x86_64(因为每个架构都需要自己的实现),但仍然具有很高的教育意义和推荐。