为什么Objective-C编译器需要在编译时知道将在对象上调用的方法的签名(如果可以将其推迟到运行时)(即动态绑定)?例如,如果我写[foo someMethod]
,为什么编译器必须知道someMethod
?的签名?
由于至少需要调用约定(使用ARC,有更多原因,但是调用约定始终是个问题)。
您可能已经被告知[foo someMethod]
转换为函数调用:
objc_msgSend(foo, @selector(someMethod))
然而,这不是正是如此。取决于返回的内容(无论是否使用结果,返回的内容都很重要),它可能会转换为许多不同的函数调用。例如,如果它返回一个对象或一个整数,它将使用objc_msgSend
,但是如果它返回一个结构(在ARM和Intel上),它将使用objc_msgSend_stret
,并且如果它在Intel(但不是ARM)上返回一个浮点数),它将使用objc_msgSend_fpret
。这是因为在不同的处理器上,调用约定(如何设置堆栈和寄存器以及存储结果的位置)取决于结果而有所不同。
这也很重要,参数是什么以及参数有多少(可以从ObjC方法名称中推断出这些数字,除非它们是varargs ...对,您也必须处理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(因为每个体系结构都需要自己的实现),但是仍然具有很高的教育意义,建议使用。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句