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



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

因为调用约定至少(对于 ARC,有更多的原因,但调用约定一直是一个问题)。

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

objc_msgSend(foo, @selector(someMethod))

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

参数

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

ObjC 可以作为更纯粹的对象模型实现,以避免所有这些(就像其他更动态的语言一样),但它会以牺牲性能(空间和时间)为代价。考虑到动态调度的水平,ObjC 可以进行方法调用,并且可以轻松地使用纯 C 机器类型,但这样做的代价是我们必须让编译器了解有关方法签名的更多细节。

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

- (MyPointObject *)point;
- (CGPoint)point;

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

有关更多背景,您可能会喜欢格雷格·帕克(Greg Parker)解释objc_msgSend_stret和objc_msgSend_fpret的文章。迈克·阿什(Mike Ash)也对这个话题进行了很好的介绍。如果你想深入这个兔子洞,你可以看到bbum对objc_msgSend的逐条指令调查。它现在已经过时了,在ARC之前,只涵盖了x86_64(因为每个架构都需要自己的实现),但仍然具有很高的教育意义,值得推荐。

最新更新