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

Zac*_*OIR 4 objective-c objective-c-runtime method-signature

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

Rob*_*ier 6

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

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

objc_msgSend(foo, @selector(someMethod))
Run Code Online (Sandbox Code Playgroud)

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

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

ObjC可以作为一个更纯粹的对象模型实现,以避免所有这些(如其他更动态的语言),但它会以性能(空间和时间)为代价.在给定动态调度级别的情况下,ObjC可以使方法调用变得非常便宜,并且可以轻松地使用纯C机器类型,但是成本是我们必须让编译器了解有关我们的方法签名的更多细节.

顺便说一句,这可能(并且经常发生)会导致非常可怕的错误.如果你有几种方法:

- (MyPointObject *)point;

- (CGPoint)point;
Run Code Online (Sandbox Code Playgroud)

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

有关更多背景信息,您可以欣赏Greg Parker的文章,解释objc_msgSend_stretobjc_msgSend_fpret.Mike Ash 对这个主题也有很好的介绍.如果你想深入了解这个兔子洞,你可以看到bbum对objc_msgSend的指导调查.它现在已经过时,在ARC之前,并且仅涵盖x86_64(因为每个架构都需要自己的实现),但仍然具有高度的教育性和推荐性.