编写高质量OC代码52建议总结:11.理解objc_msgSend的做用(消息机制)

在对象上调用方法,术语叫作“传递消息”,消息有“名称”和“选择器(方法)”,能够接收参数,还可能有返回值。OC是C的超集,C语言使用静态绑定,在编译期间就能决定运行时作调用的函数。缓存

#include <stdio.h>

void printHello() {
    printf("hello, world!\n");
}

void printGoodbye() {
    printf("Goodbye, world!\n");
}

void doTheThing(int type) {
    if (type == 0) {
        printHello();
    } else {
        printGoodbye();
    }
}

编译器在编译代码的时候就知道有printHello和printGoodbye两个函数了,会直接生成调用这些函数的指令,函数地址其实是硬编码在指令之中。

#include <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();
}

若是代码变成这样,就得使用“动态绑定”,由于所要调用的函数直到运行期才能肯定,第一个例子中,if 和 else 中都有函数调用指令,第二个例子中只有一个函数调用指令,待调用的函数地址没法硬编码在指令中,要在运行期读取出来。

给对象发送消息能够这么写:函数

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 会在接受者所属的类中搜寻其“方法列表”,若是找到与“选择器”名称相符的代码就实现,没找到就延继承体系向上查找,最终找不到,就实现“消息转发”。

备注:每一个类都有一块缓存“快速映射表”,若是稍后还向该类发送相同的消息,执行就快。优化


运行环境中一些“边界状况”,须要其余函数处理
  1.objc_msgSend_stret:若是待发送的消息要返回结构体,交给这个函数。
  2.objc_msgSend_fpre:若是消息返回的是浮点数,交给这个函数。
  3.objc_msgSendSuper:给超类发消息,用这个函数。也有与objc_msgSend_fpre和objc_msgSend_fpre等效的处理超类消息的方法。
 
编码

OC对象的每一个方法均可以看作简单的C函数,原型以下:
指针

<return_type> Class_selector(id self, SEL _cmd, ...)
每一个类里都会有一张表格,其中的指针都会指向这种函数,选择器的名称则是查表时用的“键”,objc_msgSend用这张表来寻找应该执行的方法并跳转实现。
  

“尾调用优化”:若是函数的最后一项操做是调用另一个函数,编译器会生成调转至另外一个函数所用的指令码。不会向调用堆栈中推入新的“栈帧”。
注意:只有当函数的最后一项操做仅仅是调用其余函数而不会将其返回值另作他用时


code