美文网首页
ObjC-Runtime源码注疏(一)

ObjC-Runtime源码注疏(一)

作者: 一张懵逼的脸 | 来源:发表于2020-10-09 15:45 被阅读0次

    前言

    本文注疏的源码版本为objc-781版本

    (一)从提问开始

    拿到源码后,也不知从何入手。后来想了想,就从一道我们常见的面试题开始吧。isa指针是个啥?

    从代码中,我们可以看到如下代码

    #include "isa.h"
    
    union isa_t {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        Class cls;
        uintptr_t bits;
    #if defined(ISA_BITFIELD)
        struct {
            ISA_BITFIELD;  // defined in isa.h
        };
    #endif
    };
    

    首先,isa_t就是isa指针的真正类型,它是一个联合体(union)而不是一个结构体(struct)。那么这里的第一问就来了,联合体与结构体啥区别?

    union与struct相比,实际上更省内存,但它的“省内存”是靠牺牲所有字段的独立存储空间换来的。换句话说,结构体分配内存后,所有的字段都有独立的存储空间。而联合体是所有字段公用一个存储空间。
    换句话说,当我们为cls字段赋值后,bits字段的值就不存在了。反之亦然。总之,union在内存分配时会根据自身内部所有的字段大小来进行比较,最终union会按照内存占用最大的字段的大小作为分配的依据。

    那么在这里使用联合体,说明对于后面的信息传递而言,isa_t里的所有字段,只要有一个字段有效,就可以足够进行信息传递了。

    (二)内观isa

    下面咱们再看看字段。整个isa_t中有多个字段,分别是Class cls字段,uintptr_t bits字段以及一个宏定义的匿名结构体。下面我们逐个看一下:

    Class cls 字段

    Class类型实际上也是一个结构体,在objc-private.h中有以下定义,足以说明问题

    struct objc_class;
    struct objc_object;
    
    typedef struct objc_class *Class;
    typedef struct objc_object *id;
    

    可以看到,Class实际上就是objc_class的结构体指针,而下面那句不就是常常出现的面试考题之一吗 —— “OC中id类型的实质”。从上面的定义也可以看到了,id实际上就是objc_object结构体的指针。
    对于这两个结构体,后面会有详细的阐述,此处先跳过。所以说Class cls字段实际上就是指向objc_class的指针,其描述了当前对象指向的类型。

    uintptr bits 字段

    对于这个字段,我们要结合其下面的定义一起看,也就是如下代码:

        uintptr_t bits;
    #if defined(ISA_BITFIELD)
        struct {
            ISA_BITFIELD;  // defined in isa.h
        };
    #endif
    };
    

    这里看到,在bits字段下还有一套匿名的结构体,其具体信息定义在isa.h中。我们再来具体看一下:

    # if __arm64__
    #   define ISA_MASK        0x0000000ffffffff8ULL
    #   define ISA_MAGIC_MASK  0x000003f000000001ULL
    #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    #   define ISA_BITFIELD                                                      \
          uintptr_t nonpointer        : 1;                                       \
          uintptr_t has_assoc         : 1;                                       \
          uintptr_t has_cxx_dtor      : 1;                                       \
          uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
          uintptr_t magic             : 6;                                       \
          uintptr_t weakly_referenced : 1;                                       \
          uintptr_t deallocating      : 1;                                       \
          uintptr_t has_sidetable_rc  : 1;                                       \
          uintptr_t extra_rc          : 19
    #   define RC_ONE   (1ULL<<45)
    #   define RC_HALF  (1ULL<<18)
    
    # elif __x86_64__
    #   define ISA_MASK        0x00007ffffffffff8ULL
    #   define ISA_MAGIC_MASK  0x001f800000000001ULL
    #   define ISA_MAGIC_VALUE 0x001d800000000001ULL
    #   define ISA_BITFIELD                                                        \
          uintptr_t nonpointer        : 1;                                         \
          uintptr_t has_assoc         : 1;                                         \
          uintptr_t has_cxx_dtor      : 1;                                         \
          uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
          uintptr_t magic             : 6;                                         \
          uintptr_t weakly_referenced : 1;                                         \
          uintptr_t deallocating      : 1;                                         \
          uintptr_t has_sidetable_rc  : 1;                                         \
          uintptr_t extra_rc          : 8
    #   define RC_ONE   (1ULL<<56)
    #   define RC_HALF  (1ULL<<7)
    
    # else
    #   error unknown architecture for packed isa
    # endif
    

    可以看到,ISA_BITFIELD宏定义了再arm64下和x86下分配位数不一样的一组信息。结合这些信息,以及后面会说到的objc_object对isa进行初始化时的代码,我们大致对bits有了如下说明
    首先bits就是以上所有信息的存储单元。换句话说,虽然bits只是一个uint_ptr类型的值,但实际上这个值本身可以通过不同位存储了不同的信息。从以上掩码中可以看出,shiftcls字段在不同的CPU架构中,定义了不同的长度。这其实就是存储当前对象Class地址的地方。
    所以,bits说白了就是一个掩码值。OC语言的设计者将其按不同的位数,分别赋予了不同的信息存储。这样,一个值就可以存储很多信息,大大节省了空间。

    匿名struct字段

    该字段实际上在bits里面已经有所阐述,他就是一个对bits内部具体存储的信息的分段显示。这里就不再赘述了。这里我们倒是可以好好的聊一聊上面的那些宏定义。
    首先,这里要先讲解一个概念,即“位域”的概念。以上面的代码为例

    uintptr_t nonpointer : 1;
    

    这句代码就定义了一个“位域”,它的实际意义是,在uintptr_t这个64位类型中,nonpointer字段占一位。那么其后的所有定义都是按顺序占用不同的“位数”,即has_assoc占1位、has_cxx_dtor占1位、shiftcls占44位等等。那么这些信息定义完成后,其总长度必然是一个uintptr_t的长度,也就是64


    isa.jpg

    上图展示了在不同CPU架构下的isa内,bits值的各个字段分配的长度。我们依次说明一下:

    nonpointer字段 占1位

    该字段只占一位,只有0/1之分。其含义是标识当前isa是否是一个nonpointer。如果是0,则代表当前是一个纯粹的isa指针;如果是1,则代表当前isa内存储了其他信息,而其本身指向的cls的地址需要从shiftcls字段读取。

    has_assoc字段 占1位

    该字段只占一位,只有0/1之分。其含义是标识当前拥有该isa指针的对象(objc_object,下同)是否有关联对象。1代表有,0代表没有。如果是0的话,不会执行有关关联对象的操作。

    has_cxx_dtor字段 占1位

    该字段只占一位,只有0/1之分。其含义是标识当前拥有该isa指针的对象是否有C++或ObjC的析构函数。1代表有,则对象在释放时要执行析构逻辑;0代表没有,则释放时会更快一些。

    shiftcls字段 x86占44位,arm64占33位

    该字段在不同架构下有不同的位数,但都是在isa联合体中占位最多的字段,该字段实际上存储的是一个地址。该地址指向当前拥有isa的对象的class-object。掩码ISA_MASK就是获取这段地址。

    magic字段 占位6位

    该字段用来帮助调试器来判断当前拥有该isa的对象,是一个真实的对象,还是一个没有被初始化的空间。

    weakly_referenced字段 占1位

    该字段只占一位,只有0/1之分。其含义是标识当前对象是否有或曾经有过ARC的弱引用对象。1代表有;0代表没有。如果没有的话,对象在释放时不会执行有关弱引用释放相关的步骤,因此,释放时更快。

    deallocating字段

    该字段只占一位,只有0/1之分。该字段就是为了标识出当前对象是否处于“正在释放”的阶段。

    has_sidetable_rc字段 占1位

    该字段只占一位,只有0/1之分。该字段是为了标记是否用到了sidetable来记录引用计数。当对象引用计数大于 10 时,则需要借用该变量存储进位

    extra_rc字段

    记录引用次数,当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到上面的 has_sidetable_rc。

    以上就是对isa_t的所有字段信息的解释。这里可以注意到,在arm64和x86中,shiftcls的位数相差较大。从其注释中也可以发现

    //arm64
    uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/
    //x86
    uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/
    

    在不同的CPU架构下,留给shiftcls的最大虚拟寻址空间时不一样的。我们至少可以猜测,arm架构下,系统要占领的虚拟空间相较于x86会更多
    一些,导致shiftcls可用的寻址空间被大幅压缩。

    总结

    至此,我们已经完全展示了isa指针或者说isa_t联合体的全貌,这部分的东西将直接影响到后面objc_object和objc_class的一些理解。后续,我们会持续为objc_object和objc_class的源码注疏。

    相关文章

      网友评论

          本文标题:ObjC-Runtime源码注疏(一)

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