iOS-Runtime01-基础结构isa以及相关知识(位运算、位域和union)

在笔者编写的iOS-Objective-C的本质中,我们已经了解到,所有的OC对象本质中都包含了一个isa指针,这个指针要么指向类对象,要么指向元类对象,今天,我们进一步来观察下这个isa指针内部所包含的信息。
既然要观察isa内部的信息,我们当然得去查看objc的源码,因为大家都知道isa底层的源码在Xcode中是无法看到的,笔者查看了一下最新的objc源码最新到了objc4-756.2.tar.gz。
下载之后,我们一起看下isa的底层结构吧。
首先我们在Xcode中搜索下isa,第一个搜索到的就是

Class isa  OBJC_ISA_AVAILABILITY;

然后点击Class,看到的是

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

点击objc_class,发现它是一个结构体,而且继承objc_object

struct objc_class : objc_object {
......
}

那我们就直接看objc_object:

struct objc_object {
private:
    isa_t isa;

public:
......
}

这里我们就发现了我们需要查找的目标isa_t isa;点击isa_t,我们可以发现:

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是一个union,简称共用体。而且在这个地方有个if判断,如果定义了ISA_BITFIELD,则包含一个结构体。搜索一下ISA_BITFIELD,可以找到

# 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)

由此可得,在arm64系统下,定义了ISA_BITFIELD这个结构体,这个结构体中用到的知识点就是位域,内容就是紧跟着它下面的那个部分,所以在arm64下,isa_t的整体结构如下:

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
    struct {
      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
    };
};

到这里,我们已经可以看到,OC底层在定义isa的时候,需要用到的知识点是union、位域,那所谓的位运算又在哪里呢?其实就是跟arm64系统下定义的以下几个宏有关系

#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL

了解OC本质的同学可能知道,其实在很早之前,OC对象底层结构体的isa指针,是直接指向这个对象对应的类对象或者元类对象的地址,不过在之后的版本中,苹果针对isa指针做了优化,在得到对象的isa指针地址值之后需要&上一个ISA_MASK,才能真正得到这个对象对应的类对象或者元类对象的地址值。所以这里就涉及到了位运算,相信位运算大家都了解,这也是C语言的基础知识,总结的来说就是

&与运算,任何值 &1就是本身,&0就是0
| 或运算,任何值 | 1就是1,| 0就是本身
1 & 1 => 1       1 | 1 => 1
1 & 0 => 0      0 | 1 => 1
0 & 0 => 0     0 | 0 => 0

接下来就是位域的知识点,其实很简单,就比方说上述所定义:

struct {
      uintptr_t nonpointer        : 1; /*位域*/                                 
      uintptr_t has_assoc         : 1;                                         
      uintptr_t has_cxx_dtor      : 1;                                         
      ......
    };

每一个变量占有一位,那这么做的意义在哪里呢?其实就是可以节省内存空间,大家想想,如果我们拿一个bool类型来表示一个标志位,这样的话,每一个标志位都需要4个字节,那如果我们拿一个字节中的一个位来表示这个标志位,不也是可行的吗?因为本身一个位就是用1和0表示,不就刚好代表着true和false吗,就如以下这个定义:

struct {
            char sex : 1;   /*性别,1表示男,0表示女*/
            char isLikeMike : 1;   /*是否喜欢牛奶*/
            char isThin : 1;   /*是否偏瘦*/
            char handsome : 1;   /*是否帅气*/
        } _personCharacteristic;   /*个人特征*/

这个时候,我们就可以很大程度上节约的内存空间,因为每个特诊字段,我们都只需要一个位就可以搞定。至于如何取值赋值,这里暂时不做探讨,大家有空可以自己玩玩。
接下来,我们在来看看union这个共用体是什么用法,它的大概意思就是大家一起共用一个空间,先举个简单的例子:

union {
    int year;
    int month;
    int day;
} _yearMonthDay;

int main(int argc, const char * argv[]) {
    
    
    
    @autoreleasepool {
        
        _yearMonthDay.year = 2019;
        NSLog(@"year is %d -- month is %d -- day is %d", _yearMonthDay.year, _yearMonthDay.month, _yearMonthDay.day);
        
    }
    return 0;
}

打印出来的结果就是如下:

year is 2019 -- month is 2019 -- day is 2019

所以从这里,大家应该知道了union的用法。
那最后一个问题就是union和struct一起用,会起到一个什么作用呢?我们回到isa_t的定义上来:

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
    struct {
      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
    };
};

我们可以看到isa_t中结构体部分的总大小是64位,uintptr_t这个其实是

typedef unsigned long           uintptr_t;

也是64位,所以结构体和bits是大小一致的 这样就符合了我们的共用体规则。这样的话,后面有关于所有结构体内位运算的处理,我们都可以用bits来代替,不需要把结构体中的每一个字段读取出来,而结构体的主要内容,就是使我们的isa_t的定义有很高的可读性。这也就是isa_t将union和struct结合使用的核心意义所在!
还有一个补充的一点就是结构体内的各个字段的意义:


WeChat9fb7a77bd10134dc221ad4e2d75623e6.png

关于Runtime中isa的基本底层结构的分享暂时就到这里,后面笔者会继续分享有关于Runtime底层的其他更多的知识点,希望对大家有所帮助,如果分享的地方有偏差的,欢迎大家一起怒!!!

你可能感兴趣的:(iOS-Runtime01-基础结构isa以及相关知识(位运算、位域和union))