Objective-c方法调用本质上是消息传递。消息包括消息名称name
,选择器selector
(其实就是函数指针)。传递的消息能够接受参数,也可能有返回值。缓存
要理解OC的消息传递,就该说一下C语言的函数调用方式,毕竟OC是C语言的延伸语言。C语言使用静态绑定Static binding
进行函数调用,说人话就是C语言在编译期就能决定运行时要调用的函数。好比:架构
#import <stdio.h>
void printHello() {
printf("Hello,world!\n");
}
void printGoodbye() {
printf("Goodbye,world!\n");
}
void doSomeThing(int type) {
if (type == 0) {
printHello();
}else {
printGoodbye();
}
return 0;
}
复制代码
若是不考虑内联函数的状况(C语言中为提升函数调用的效率,直接将函数体对函数调用进行替换的方式),那么在C语言中,编译器在编译期就已经知道程序中有printHello和printGoodbye这两个函数了,因而就能够直接调用这两个函数的指令,实际上这两个函数的地址是被硬编码在指令里面的。函数
咱们把上述例子改变一下:性能
#import <stdio.h>
void printHello() {
printf("Hello,world!\n");
}
void printGoodbye() {
printf("Goodbye,world!\n");
}
void doSomeThing(int type) {
void (*fnc)();
if (type == 0) {
fnc = printHello;
}else {
fnc = printGoodbye;
}
fnc();
return 0;
}
复制代码
这时候就使用了动态绑定Dynamic binding
,所调用的函数要到运行期才能肯定。在第二个例子的代码中,只有一个函数调用指令,而且待调用函数的函数地址并不能硬编码在指令中,而是要等到运行期才能读取。优化
在OC中,传递消息其实就是用动态绑定机制来决定须要调用的方法。在OC底层,全部的方法都是普通的C语言函数,对象收到消息后,调用哪一个方法彻底由运行时决定,甚至在程序运行的时候发生改变,因此OC是一门动态语言。ui
一个典型的方法调用或者说给对象发送消息能够这样来写:编码
id returnValue = [someObj messageName:parameter];
复制代码
someObj
叫作接收者
,messageName
是选择器
,选择器及其参数,一块儿被称为消息
(Message)。编译器看到这条消息后,会把它转化为一条标准的C语言函数调用,这个是整个OC运行时消息传递机制的核心,也就是objc_msgSend
,原型以下:spa
void objc_msgSend(id self,SEL cmd,...) 复制代码
objc_msgSend
是一个参数可变函数,能接受两个或两个以上参数。第一个参数是接受者,第二个参数是选择器,其中SEL
为选择器的类型,后面就是消息中的参数了,顺序不变。因此把刚才那个典型的方法调用转换以下:指针
id returnValue = objc_msgSend(someObj,
@selector(messageName:),
parameter);
复制代码
objc_msgSend
函数会依据接受者和选择器的类型调用适当的方法。此时,objc_msgSend
会在接收者所属的类中搜寻一个方法列表,若是能找到和选择器名称相符合的方法,就跳转到其实现的代码。若是找不到,就沿着继承体系继续向上查找,等找到合适的方法后再跳转。若是这时候仍是找不到符合的方法,就执行消息转发(message forwarding)操做,这个会在下一篇文章中详解。下面用一个流程图说明上述过程:code
在整个过程当中,objc_msgSend会将匹配结果缓存在一个快速映射表
里,每个类都有一块这样的缓存。以后同一个类发送相同选择器的消息时,执行起来就会更快。但实际上,这种方式的速度仍是不如静态绑定
那么快,但大多数状况下,消息发送并非一个应用程序的性能瓶颈,若是真的出现了性能问题,能够编写单纯的C语言函数,在调用时根据须要,传入OC对象便可。
一些边界状况,OC的运行环境会提供另一些对应的函数处理:
objc_msgSend_stret
:处理待发送消息须要返回结构体的状况,但当结构体过大,致使CPU寄存器没法容纳时,会把消息交给另外一个函数派发,把返回的机构体经过分配在栈上的某个变量处理。objc_msgSend_fpret
:处理待发送消息须要返回浮点数的状况。在某些架构的CPU中,须要对浮点寄存器作一些特殊处理,因此这时候调用objc_msgSend
并不合适。objc_msgSendSuper
:向父类发送消息。objc_msgSend
找到对应的方法调用实现后,会直接跳转过去,OC中每个方法均可以理解为是一个简单的C语言函数,原型以下:
<return_type> Class_selecotor(id self,SEL _cmd,...)
复制代码
每一个类的方法列表中的指针都会指向这种函数,选择器就是查表时候所用的“键”。
这里咱们发现,方法的原型模样和objc_msgSend
有些相似,这不是巧合,而是OC利用了尾调用优化。
若是某个函数最后一项操做也是调用另外一个函数,那么就能够利用尾调用优化了。编译器会生成跳转到另外一个函数所须要的指令码,并且不会向调用栈中推入新的栈帧。这里有几个须要解释的地方:
objc_msgSend
准备栈帧
,是一个进栈的过程。好比最后返回
return [self message:someMsg];
时能够进行尾调用优化,但return [self message:someMsg]+1;
这种状况不行,由于虽然调用了函数,但最终调用的函数是为返回值作准备的。