本文是笔者阅读《Effective Objective-C》时对Item 11部分内容的翻译,以作为个人学习笔记。
Item 11: Understand the Role of objc_msgSend
本节的重点是obj_msgSend函数,该函数是Objective-C消息传递机制中的核心部分。
消息传递是编写程序时我们最经常做的事,Objective-C作为C的超集,要了解它,首先要了解C语言静态绑定和动态绑定。
静态绑定意味着在编译时就已经知道要调用哪个方法了。比如如下代码:
#import <stdio.h>
void printHello() {
printf("Hello, world!\n");
}
void printGoodbye() {
printf("Goodbye, world!\n");
}
void doTheThing(int type) {
if (type == 0) {
printHello();
} else {
printGoodbye();
}
return 0;
}
但是如果写成下面这个样子:
#import <stdio.h>
void printHello() {
printf("Hello, world!\n");
}
void printGoodbye() {
printf("Goodbye, world!\n");
}
void doTheThing(int type) {
void (*fnc)();
if (type == 0) {
fnc = printHello;
} else {
fnc = printGoodbye;
}
fnc();
return 0;
}
这里就用到了动态绑定,在运行之前调用哪一个函数是未知的。这两个例子的区别就在,第二个例子中只有一个函数调用,并且只有在运行时再去读取函数的地址而不是被编写成死的代码。
而Objective-C中消息传递就是由动态绑定实现的,所有的方法在底层都是由C函数实现的,在消息传递时调用哪个方法都是在运行时决定的。这种机制也使得Objective-C成了真正的动态语言。
消息传递的一般形式如下:
id returnValue = [someObject messageName:parameter];
someObject表示接收器,messgeName表示选择器,parameter就是参数了。
在底层编译器就会将其转化成为标准的C函数objc_msgSend,原型如下:
void objc_msgSend(id self, SEL cmd, ...)
这个函数会从接受者的方法列表中寻找与选择器名称匹配的方法,找到后就会跳到实现部分。
调用形式如下:
id returnValue = objc_msgSend(someObject,
@selector(messageName:),
parameter);
另外,objc_msgSend会将结果保存到一个快表里,以便未来的消息能够迅速找到对应的类和选择器。因此
这种方式不会比静态绑定慢很多。
处理其他情况的Objective-C运行时方法还有:
objc_msgSend_stret
用来发送返回结构体的消息。
objc_msgSend_fpret
用来发送返回浮点数的消息。
objc_msgSend_Super
用来向超类发送消息。
而之所以objc_msgSend能够在找到匹配的函数后就跳转过去是因为Objective-C中所有的类的方法都可以视为一个C函数,原型如下:
<return_type> Class_selector(id self, SEL _cmd, ...)
名称可能有所不同,这样写是为了方便解释其结构。每个类中都由一张表指向这些函数,objc_msgSend就利用这张表进行跳转。
并且其中还利用了"尾部优化"机制。这种机制当函数最终操作是调用另一个函数时使用。编译器会生成跳转向下一个函数的指令码,而不是在栈中再插入栈帧。当然只有当函数最后一项操作是调用其他函数,而且不会将返回值另作它用时才会使用“尾部优化”。
最后Things To Remember:
消息由接收器,选择器和参数构成,消息传递与方法调用等价。
所有的消息传递都由动态消息调度系统实现,形式如上所言。