为什么Objective-C编译器需要在编译时知道在将对象推迟到运行时(即动态绑定)时将在对象上调用的方法的签名?例如,如果我写[foo someMethod]
,为什么编译器需要知道someMethod
的签名?
答案 0 :(得分:6)
由于调用约定至少(使用ARC,有更多原因,但调用约定一直是个问题)。
您可能被告知[foo someMethod]
已转换为函数调用:
objc_msgSend(foo, @selector(someMethod))
然而, 。它可能会转换为许多不同的函数调用,具体取决于它返回的内容(无论是否使用结果,返回的内容都是重要的)。例如,如果它返回一个对象或整数,它将使用objc_msgSend
,但如果它返回一个结构(在ARM和Intel上),它将使用objc_msgSend_stret
,如果它返回一个英特尔的浮点数(但我认为不是ARM),它将使用objc_msgSend_fpret
。这都是因为在不同的处理器上,调用约定(如何设置堆栈和寄存器以及存储结果的位置)因结果而异。
参数是什么以及有多少(这个数字可以从ObjC方法名称中推断,除非它们是varargs ......对,你也必须处理varargs)。在某些处理器上,前几个参数可以放在寄存器中,而后面的参数可以放在堆栈中。如果你的函数采用了varargs,那么调用约定可能会有所不同。为了编译函数调用,必须知道所有这些。
ObjC可以作为一个更纯粹的对象模型来实现,以避免所有这些(如其他更动态的语言那样),但它会以性能(空间和时间)为代价。 ObjC可以根据动态调度的级别使方法调用变得非常便宜,并且可以轻松地使用纯C机器类型,但是成本是我们必须让编译器了解有关我们方法签名的更多细节。
顺便说一句,这可能(并且经常会发生)导致真正可怕的错误。如果你有几种方法:- (MyPointObject *)point;
- (CGPoint)point;
也许它们在完全不同的文件中被定义为不同类的方法。但是如果编译器选择了错误的定义(例如当你向id
发送消息时),那么从-point
返回的结果可能是完全垃圾。这是一个非常非常难以弄清楚它何时发生的错误(我已经把它发生在我身上)。
如需更多背景知识,您可以享受Greg Parker的article explaining objc_msgSend_stret和objc_msgSend_fpret。 Mike Ash也有an excellent introduction这个话题。如果你想深入这个兔子洞,你可以看到bbum的instruction-by-instruction investigation of objc_msgSend。它现在已经过时,在ARC之前,并且仅涵盖x86_64(因为每个架构都需要自己的实现),但仍然具有很高的教育性和推荐。