美文网首页iOS奇思怪巧征服iOS
高效编写代码的方法(九):了解objc_msgSend

高效编写代码的方法(九):了解objc_msgSend

作者: 蜂猴 | 来源:发表于2016-05-04 20:04 被阅读3111次

    在OC中我们调用方法也叫作给对象发消息,消息包含了名字,选择器,参数及返回值等信息。

    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;  
    } 
    

    在C语言中,在不考虑使用内联函数的情况下,printHello和printGoodbye函数都是已知的。在调用时,编译器直接发出指令去进行调用,函数的地址通过硬解码得到。

    现在换一种写法:

    #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;  
    } 
    

    以上代码则使用了动态绑定的方法,直到运行的时候,fnc函数具体是什么函数是未知的。与第一段代码不同的是,这里获得函数地址的方法不能硬解码获得,而是在运行期间得到。

    OC中

    OC中,对象调用方法,也叫给对象发送消息,实际上是使用了动态绑定机制。在底层,所有方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用哪个方法则完全于运行期决定,甚至可以在程序运行时改变,这些特性使得Objective-C成为一门真正的动态语言。
    通常我们在OC中这样发送消息:

    id returnValue = [someObject messageName:parameter];
    

    someObject是消息的接受者,messageName是一个选择器,parameter则为参数。选择器+参数 就是我们所称为的消息。
    在底层,编译器将我们的消息转换文标准的C函数形式,如下:

    void objc_msgSend(id self,SEL cmd,…)
    

    self 为消息接收者,cmd为选择器,省略号为参数,表示可变长度参数。
    因此,以上的消息转换为标准的C函数后如下:

    id returnValue = objc_msgSend(someObject,@selector(messageName),paramter)
    

    之所以objc_msgSend方法总能找到正确的函数去执行,原因如下:
    其实每个类中都有一张方法列表去存储这个类中有的方法,当发出objc_msgSend
    方法时候,就会顺着列表去找这个方法是否存在,如果不存在,则向该类的父类继续查找,直到找到位置。如果始终没有找到方法,那么就会进入到消息转发机制(后续知识,以后章节会介绍) 。
    OC runtime还有一个机制在于方法缓存,每调用完这个方法后,一个方法映射就会被缓存起来,如果之后调用相同的方法,那么就能直接从映射表里确定方法的位置,而不用每次都需要查找,这样执行速度会快一点。

    几个特殊方法

    objc_msgSend_stret
    如果待发送的消息要返回结构体,那么可交由此函数处理。只有当CPU的寄存器能够容纳得下消息返回类型时,这个函数才能处理此消息。若是返回值无法容纳于CPU寄存器中(比如说返回的结构体太大了),那么就由另一个函数执行派发。此时,那个函数会通过分配在栈上的某个变量来处理消息所返回的结构体。

    objc_msgSend_fpret
    如果消息返回的是浮点数,那么可交由此函数处理。在某些架构的CPU中调用函数时,需要对“浮点数寄存器”(floating-point register)做特殊处理,也就是说,通常所用的objc_msgSend在这种情况下并不合适。这个函数是为了处理x86等架构CPU中某些令人稍觉惊讶的奇怪状况。

    objc_msgSendSuper
    如果要给超类发消息,例如[super message:parameter],那么就交由此函数处理。也有另外两个与objc_msgSend_stret和objc_msgSend_fpret等效的函数,用于处理发给super的相应消息。

    以上内容摘抄自网上翻译,因为英文原文这部分实在是不太好理解。
    我觉得可以简单的按照字面意思来进行选择,比如你希望函数返回体为结构体,那么就使用objc_msgSend_stret,否则有几率会崩溃。返回值为浮点数时也是相同道理。

    上文说过,当找到相应的方法时,会跳转过去。之所以可以这样实现,是因为每一个Objective-C函数都可以看作是一个简单的C函数,原型如下:

    <return_type> Class_selector(id self,SEL _cmd,...)
    

    以上Class及selector的命名是为了方便理解。每个类中都有一张类似于字典的方法表,而selector就相当于查找方法的key,objc_msgSend函数就是通过查这张表来实现跳转的。之所以以上原型和objc_msgSend方法长的非常相像,是为了更好使用tail-call技术来时方法的跳转更加优化。

    如果某函数的最后一项操作是调用另外一个函数,那么就可以运用“tail-call”技术。
    此时编译器会生成调转至另一函数所需的指令码,而不会向调用堆栈中推入新的“栈帧”。tail-call使用的条件比较苛刻,除了要求函数的最后一项操作是调用另外一个函数外,,并且要求另外一个函数不是有返回值的函数类型。tail-call对objc_msgSend非常关键,如果不这么做的话,那么每次调用Objective-C方法之前,都需要为调用objc_msgSend函数准备“栈帧”,若是不优化,还会过早地发生“栈溢出”(stack overflow)现象。

    在写OC中,我们其实并不需要了解那么多底层的东西,但是我们需要知道调用一个方法之后,OC底层都发生了什么。

    总结

    • 1 一个消息包含接受者,选择子和参数。调用一个方法相当于像对象发送一条消息。
    • 2 当发送消息是,动态绑定机制会帮助我们查找方法的实现并进行运行。

    相关文章

      网友评论

      • 蜂猴:@Y_Werewolf 第一篇讲import的文章我已经说到过
        纯粹翻译+学习+记录+交流
        Jax_YD:嗯嗯,你这样很容易让人误解,虽然你不是有意抄袭,但是最起码要注明出处。烂大街的知识点没人在乎,哪怕是当做自己的笔记也好。但是这种经典的著作还是要注明出处的,是对原作者最起码的尊重。
      • 小時間光:博主,书上的这两段代码有错误,可以改成:
        void printHello() {
        printf("Hello, world!\n");
        }

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

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

        或者改成这样:
        void printHello() {
        printf("Hello, world!\n");
        }

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

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

        int main(int argc, char * argv[]) {

        doTheThing(0);
        return 0;
        }
        Jax_YD:纯属抄袭,一个字都不带落下的《Effective Objective-C 2.0》
        蜂猴:谢谢哈 return 0 是多余了

      本文标题:高效编写代码的方法(九):了解objc_msgSend

      本文链接:https://www.haomeiwen.com/subject/mzqrrttx.html