Objective-C 小记(1)Messaging

作者: KylinRoc | 来源:发表于2017-01-27 00:03 被阅读99次

    众所周知,在 Objective-C 中,如下的消息发送

    [receiver message];
    

    会被编译器转换为

    objc_msgSend(receiver, @selector(message));
    

    这样,实际的函数调用在运行时(runtime)才能确定,即所谓的动态绑定

    要了解 objc_msgSend 的具体实现,需要先了解 Objective-C 运行时中对象,类和方法的实现以及它们的关系。

    对象

    在 Objective-C 中,有一个类型 id,用来表示指向对象的指针,它的定义能在 Objective-C 运行时的公开头文件 objc.h 中找到

    /// A pointer to an instance of a class.
    typedef struct objc_object *id;
    

    显然 objc_object 结构体就是 Objective-C 运行时中用来表示对象的数据结构了,它的定义也能在 objc.h 中看到

    /// Represents an instance of a class.
    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    

    Class 类型代表的肯定是类,isa 这个名字也很直观(is a some class)。因为结构体只是一个内存排布的约定,所以任何第一个成员是 Class 类型的结构体都可以视作为对象。

    类和元类

    objc.h 中,可以看到 Class 的定义

    /// An opaque type that represents an Objective-C class.
    typedef struct objc_class *Class;
    

    所以 Class 准确来说是指向类的指针,而 objc_class 结构体就是类真正的实现了。在公开头文件 runtime.h 中,可以看到 objc_class 结构体的定义

    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class super_class                                        OBJC2_UNAVAILABLE;
        const char *name                                         OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
        struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
        struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    /* Use `Class` instead of `struct objc_class *` */
    

    注意到 objc_class 结构体的第一个成员也是 Class isa,这样它也能被当作是一个对象。接下来是 Class super_class,用来指向父类。再者可以看到 objc_class 结构体有一个叫 methodLists 的成员,用来存放类的实例方法。

    现在就有了两个问题,类的 isa 指向什么?类的类方法存放在哪里呢?这两个问题的答案都是「元类(meta-class)」。

    每个类都有一个对应的元类,类的 isa 指向其对应的元类,对应的元类中存放着类的类方法。显然又有了新的问题,元类的类型也是 Class,那它的 isasuper_class 指向哪里呢?元类的 super_class 很自然的指向其对应类的父类的元类,而 isa 则指向「根类(root class)」所对应的元类。

    这篇文章对元类做了非常好的解释,并且有一张很好的对对象、类和元类关系的图示

    class diagram.jpg

    可以看到,根类的 super_class 指向 nil,根类对应的元类的 super_class 指向根类。

    方法

    objc_class 结构体中可以看到存放方法的成员类型是 objc_method_list,它的定义能在 runtime.h 中找到

    struct objc_method_list {
        struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;
    
        int method_count                                         OBJC2_UNAVAILABLE;
    #ifdef __LP64__
        int space                                                OBJC2_UNAVAILABLE;
    #endif
        /* variable length structure */
        struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
    }                                                            OBJC2_UNAVAILABLE;
    

    很显然,objc_method 结构体就是表示方法的数据结构,runtime.h 中对 Method 类型的定义也证明了这点

    /// An opaque type that represents a method in a class definition.
    typedef struct objc_method *Method;
    

    同样在 runtime.h 中,可以找到 objc_method 结构体的定义

    struct objc_method {
        SEL method_name                                          OBJC2_UNAVAILABLE;
        char *method_types                                       OBJC2_UNAVAILABLE;
        IMP method_imp                                           OBJC2_UNAVAILABLE;
    }                                                            OBJC2_UNAVAILABLE;
    

    其中 SEL 的定义可以在 objc.h 中找到

    /// An opaque type that represents a method selector.
    typedef struct objc_selector *SEL;
    

    实际上,objc_selector 结构体根本就没有实现,这里就是它唯一存在的地方了。所以 SEL 可以看作是 void *

    method_types 这个成员是方法的编码,具体的规则在官方文档里有一些说明。

    IMPobjc.h 中的定义是这个样子的

    typedef id (*IMP)(id, SEL, ...); 
    

    所以 IMP 其实是一个函数指针,第一个参数是一个对象,第二个参数是一个方法名。这两个参数其实就对应着 self_cmd

    这么看来,方法的表示还是很清晰的,就是方法名、方法的类型和具体实现。

    消息发送

    首先看一下公开头文件 message.hobjc_msgSend 的声明

    id objc_msgSend(id self, SEL _cmd, ...);
    

    然后可以继续 objc_msgSend 函数的大概逻辑了:

    1. 检查 self 是否为 nil,是的话直接返回 nil,这就是为什么可以给 nil 发消息;
    2. 通过 selfisa 获取到类(或元类);
    3. 使用 _cmd 在类(或元类)的方法中进行查找,没有找到的话,通过类(或元类)的 super_class 继续查找;
    4. 找到了对应的方法,调用其 IMP
    5. 没有找到对应的方法,进入动态方法解析甚至之后的消息转发过程。

    每次都要查找,对一个会被调用成千上万次的函数来说,开销实在是太大了。所以就要给它加上缓存,也就是 objc_class 结构体中的 struct objc_cache *cache。可以在 runtime.h 中看到 objc_cache 结构体的信息

    typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;
    
    #define CACHE_BUCKET_NAME(B)  ((B)->method_name)
    #define CACHE_BUCKET_IMP(B)   ((B)->method_imp)
    #define CACHE_BUCKET_VALID(B) (B)
    #ifndef __LP64__
    #define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask))
    #else
    #define CACHE_HASH(sel, mask) (((unsigned int)((uintptr_t)(sel)>>3)) & (mask))
    #endif
    struct objc_cache {
        unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
        unsigned int occupied                                    OBJC2_UNAVAILABLE;
        Method buckets[1]                                        OBJC2_UNAVAILABLE;
    };
    

    所以 objc_msgSend 加上缓存的逻辑后:

    1. 检查 self 是否为 nil,是的话直接返回 nil
    2. 检查缓存中是否有对应的 _cmd,有的话,调用其 IMP 并返回;
    3. 缓存未命中,通过 selfisa 获取到类(或元类);
    4. 使用 _cmd 在类(或元类)的方法中进行查找,没有找到的话,通过类(或元类)的 super_class 继续查找;
    5. 找到了对应的方法,将其存入缓存,调用其 IMP
    6. 没有找到对应的方法,进入动态方法解析甚至之后的消息转发过程。

    总结

    知道了 Objective-C 中对象和类是如何表示和关联后,objc_msgSend 的原理和逻辑还是比较清晰的。之所以 Objective-C 中对象和类是如此表示,其实是一开始是受到 Smalltalk 的影响,很多东西都是直接照搬过来(甚至消息发送的语法,nil 等)。

    上面的代码中都可以看到 OBJC2_UNAVAILABLE 这个宏,宏如其名,标上这个宏的东西其实是在 Objective-C 2.0 也就是我们现在所使用的 Objective-C 中,是过时的。现在的实现在细节方面已经有了较大的变化,但整体的思路并没有改变,所以这篇文章中的逻辑在现在的实现中并没有改变。

    相关文章

      网友评论

      本文标题:Objective-C 小记(1)Messaging

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