美文网首页
Runtime消息发送和消息转发

Runtime消息发送和消息转发

作者: 大象豆豆 | 来源:发表于2022-02-19 18:48 被阅读0次

    runtime源码下载

    一、理解类对象

    类对象
    struct objc_class {
        Class _Nonnull isa  // 指向元类;
    
    #if !__OBJC2__
        Class _Nullable super_class                             // 指向父类;
        const char * _Nonnull name                             // 类名;
        long version                                             // 类的版本信息;
        long info                                               
        long instance_size                                       // 类的实例变量大小;
        struct objc_ivar_list * _Nullable ivars                  // 成员变量列表;
        struct objc_method_list * _Nullable * _Nullable methodList  // 对象方法列表;
        struct objc_cache * _Nonnull cache                       // 最近使用的方法缓存,优先在这里查找方法;
        struct objc_protocol_list * _Nullable protocols          // 协议列表;
    #endif
    
    } OBJC2_UNAVAILABLE;
    
    

    1,objc对象isa 指针指向他的类对象。
    2,类对象中也有一个isa指针指向它的元类(meta class),即类对象是元类的实例。
    3,元类内部存放的是类方法列表,isa指针指向根元类,根元类的isa指针指向自己,superclass指针指向NSObject类。
    4,根对象就是NSObject,它的superclass指针指向nil。

    二、消息发送

    向对象传递消息,会使用动态绑定机制来决定决定需要调用的方法,在底层,所有方法都是C语言函数,对象收到消息后,究竟调用那个方法取决于运行期,所以Objective-C是一门真正的动态语言。

    给对象发送消息,编辑器会将其转换为一条C语言函数调用,objc_msgSend,原型:

    void objc msgSend (id self, SEL cmd, ...)

    第一个参数代表接收者,第二个参数代表选择子(SEL 是选择子的类型),后续参数就是消息中的那些参数,其顺序不变。

    objc_msgSend 函数会依据接收者与选择子的类型来调用适当的方法。在接收者所属的类中搜寻其“方法列表”(list of methods),如果能找到与选择子名称相符的方法,就跳至其实现代码。若是找不到,那就沿着继承体系继续向上查找,等找到合适的方法之后再跳转。如果最终还是找不到相符的方法,那就执行“消息转发” (messags forwarding)操作。
    这么说来,想调用一个方法似乎需要很多步骤。所幸 objc_ msgSend 会将匹配结果缓在“快速映射表”(ftast map)里面,每个类都有这样一块缓存,若是稍后还向该类发送与选择子相同的消息,那么执行起来就很快了。

    这只是部分消息的调用过程,其他“边界情况”(edge case)则需要交由 Objective-C 运行环境中的另一些函数来处理:

    • objc_msgSend_stret。如果待发送的消息要返回结构体,那么可交由此函数处理。只有当CPU 的寄存器能够容纳得下消息返回类型时,这个函数才能处理此消息。若是返回值无法容纳于 CPU 寄存器中(比如说返回的结构体太大了),那么就由另一个函数执行派发。此时,那个函数会通过分配在栈上的某个变量来处理消息所返回的结构体。
    • objc_msgSend_fpret。 如果消息返回的是浮点数,那么可交由此函数处理。在某些架构的 CPU 中调用函数时,需要对 “浮点数寄存器(foating-point register)做特殊处理,也就是说,通常所用的 objc_msgSend 在这种情况下并不合适。这个函数是为了处理
      ×86 等架构 CPU 中某些令人稍觉惊讶的奇怪状况。
    • objc_ msgSendSuper。如果要给超类发消息,例如 [super message:parameter],那么就交由此函数处理。也有另外两个与 objc_msgSend_ stret 和 objc_msgSend fpret 等效的函数,用于处理发给 super 的相应消息。

    刚才提到,objc_msgSend 等函数一旦找到应该调用的方法文现”之后,就会“跳转过去”。之所以能这样做,是因为 Objiective-C 对象的每个方法都可以视为简单的C 函数,其原型如下:
    <return type> Class selector (id self, SEI _end, ...)
    真正的函数名和上面写的可能不太一样,用 “类”(class)和“选择子”(selector)来命名是想解释其工作原理。每个类里都有一张表格,其中的指针都会指向这种C语言函数,而选择子的名称则是查表时所用的“键”。objc_msgSend 等函数正是通过这张表格来寻找应该执行的方法并跳至其实现的。请注意,原型的样子和 objc_msgSend函数很像。这不是巧合,而是为了利用 “尾调用优化”(tail-call optimization)技术,令“跳至方法实现” 这一操作变得更简单些。

    如果某两数的最后一项操作是调用另外一个函数,那么就可以运用 “尾调用优化”技术。编译器会生成调转至另一两数所需的指令码,而且不会向调用堆栈中推人新的“栈帧” (frame stack)。只有当某两数的最后一个操作仅仅是调用其他两数而不会将其返回值另作他用时,才能执行一尾调用优化”。这项优化对 obje msgSend 非常关键,如果不这么做的话,那么每次调用 Objective-C 方法之前,都需要为调用 objc_ msgSend 函数准备“栈帧”,大家在“栈踪迹”(stack trace)中可以看到这种“栈帧”。此外,若是不优化,还会过早地发生“栈溢出”(stack overflow)现象。

    三、消息转发

    书籍:Effective Objective-C 2.0

    相关文章

      网友评论

          本文标题:Runtime消息发送和消息转发

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