初步理解objc_msgSend

Objective-c方法调用本质上是消息传递。消息包括消息名称name,选择器selector(其实就是函数指针)。传递的消息能够接受参数,也可能有返回值。缓存

C语言的函数调用

要理解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,所调用的函数要到运行期才能肯定。在第二个例子的代码中,只有一个函数调用指令,而且待调用函数的函数地址并不能硬编码在指令中,而是要等到运行期才能读取。优化

Objective-c的消息传递

在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利用了尾调用优化

尾调用优化

若是某个函数最后一项操做也是调用另外一个函数,那么就能够利用尾调用优化了。编译器会生成跳转到另外一个函数所须要的指令码,并且不会向调用栈中推入新的栈帧。这里有几个须要解释的地方:

  • OC的方法调用会为objc_msgSend准备栈帧,是一个进栈的过程。
  • 当某个函数最后一项操做也是调用另外一个函数,而且调用的函数不做为返回值另作他用,才能使用尾调用优化。

    好比最后返回return [self message:someMsg];时能够进行尾调用优化,但return [self message:someMsg]+1;这种状况不行,由于虽然调用了函数,但最终调用的函数是为返回值作准备的。

  • 尾调用优化会经过复用栈帧,避免调用方法时不断的进栈形成栈溢出最后程序崩溃。
相关文章
相关标签/搜索