在对象上调用方法是Object-C中经常使用的功能。用Object-C的术语来说,这叫做“消息传递”。消息有“名称”或“选择子”,可以接受参数,而且可能还有返回值。由于OC是C的超集,所以最好先理解C语言的函数调用方式。C语言使用“静态绑定”,也就是说,在编译期就能决定运行时所应调用的函数。以下列代码为例:
#import <stdio.h>
void printfHello() {
printf("Hello,world!\n");
}
void printfGoodbye() {
printf("Goodbye,world!\n");
}
void doTheThing(int type) {
if(type ==0) {
printfHello();
} else {
printfGoodbye():
}
return 0;
}
如果不考虑“内联”,那么编译器在编译代码的时候就已经知道程序中有printfHello与printfGoodbye这两个函数了,于是会直接生成调用这些函数的指令。而函数地址实际上是硬编码在指令之中的。若是将刚才那段代码写成下面这样,会如何呢? #import <stdio.h>
void printfHello() {
printf("Hello,world!\n");
}
void printfGoodbye() {
printf("Goodbye,world!\n");
}
void doTheThing(int type) {
void (*fnc) ();
if(type ==0) {
fnc = printfHello;
} else {
fnc = printfGoodbye():
}
fnc();
return 0;
}
这时就得使用“动态绑定”,因为所要调用的函数直到运行期才能确定。编译期在这种情况下生成的指令与刚才那个例子不同,在第一个例子中,if与else语句里都有函数调用指令。而在第二个例子中,只有一个函数调用指令,不过待调用的函数地址无法硬编码在指令中,而是要在运行期读取出来。
在OC中,如果向某对象传递消息,那就会使用动态绑定机制来决定需要调用的方法。在底层,所有方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用哪个方法则完全取决于运行期决定,甚至可以在程序运行时改变,这些特性使得OC成为一门真正的动态语言。
给对象发送消息可以这样来写:
id returnValue = [ someObject messageName:parameter];
在本例中,someObject叫做“接受者”,messageName叫做“选择者”,选择子和参数合起来叫做“消息”。编译器看到此消息后,将其转换为一条标准的C语言函数调用,所调用的函数乃是消息传递机制中的核心函数,叫做“objc_msgSend”,原型如下:
void objc_msgSend(id self , SEL cmd ,……)
编译器会把刚才那个例子中的消息转换为如下函数:
id returnValue = objc_msgSend(someObject , @selector(messageName:) , parameter);
objc_msgSend函数会依据接收者与选择子的类型来调用适当的方法。为了完成此操作,该方法需要在接受者所属的类中搜寻其“方法列表”,如果能找到与选择子名称相符的方法,就跳至其实现代码。若是找不到,那就沿着继承体系继续向上查找,等找到合适的方法之后再跳转。如果最终还是找不到相符的方法,那就执行“消息转发”操作。等会讲消息转发
这么说来,想调用一个方法似乎需要很多步骤。所幸objc_msgSend会将匹配结果缓存在“快速映射表”里面,每个类都有这样一块缓存,若是稍后还向该类发送与选择子相同的消息,那么执行起来就很快了。当然了这种“快速执行路径”还是不如“静态绑定的函数调用操作”那样迅速,不过只要把选择子缓存起来了,也就不会慢很多,实际上,消息派发并非应用程序的瓶颈所在。假如真是个瓶颈的话,那你可只编写纯C函数,在调用时根据需要,把OC对象的状态传进去。
前面讲的这部分内容之描述了部分消息的调用过程,其他边界情况则需要交由OC运行环境中的另一些函数来处理:
*objc_msgSend_stret 如果待发送的消息要返回结构体,那么可交由此函数处理。只有当CPU的寄存器能够容纳的消息返回类型时,这个函数才能处理此消息。若是返回值无法容纳于CPU寄存器中,比如返回的结构体太大了,那么久由另一个函数执行派发。此时那个函数会通过分配在栈上的某个变量来处理消息所返回的结构体。
*objc_msgSend_fpret 如果消息返回的是浮点数,那么可交由此函数处理。在某些架构的CPU中调用函数时,需要对“浮点数寄存器”做特殊处理,也就是说objc_msgSend在这种情况下并不合适。这个函数是为了处理x86等架构CPU中某些令人惊讶的奇怪状况。
*objc_msgSendSuper 如果要给超类发消息,例如[super message:parameter],那么就交由此函数处理。也有另外两个与objc_msgSend_stret 和objc_msgSend_fpret等效的函数,用于处理发给super的相应的消息。
刚才提到的objc_msgSend等函数一旦找到应该调用的方法实现之后,就会跳转过去,之所以能这么做,是因为OC对象的每个方法都可以视为简单的C函数,其原型如下:
<return_type> Class_selector(id self, SEL _cmd , ……)
每个类里都有一张表格,其中的指针都会指向这种函数,而选择子的名称则是查表时所用的“键”。