iOS 底层原理:isa结构分析

在对objc源码实现的探索的过程中,发现了一个很特别的实现,就是isa。isa 是将对象内存空间与 class 之间联结起来的桥梁,而他的实现也很精妙,在有限的存储空间(一个寄存器的存储空间,在 64 位架构为 16 个字节,在 32 位的架构为 8 个字节)里,因此节省了很多内存空间。

首先,我们先了解一下联合体位域

联合体

我们知道结构体(Struct)是一种构造类型或复杂类型,它可以包含多个类型不同的成员。在C语言中,还有另外一种和结构体非常类似的语法,叫做共用体(Union)。共用体有时也被称为联合或者联合体,它的定义格式为:

union 共用体名{
    成员列表
};

结构体和联合体的区别在于:结构体的各个成员会占用不同的内存空间,互相之间没有影响,处于共存关系;而联合体的所有成员占用同一段内存空间,修改一个成员的内容会影响其余所有成员,各个成员之间处于互斥关系。
结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。
下面我们通过一段代码验证一下:

union MYUnion{
    double d;
    int i;
    char c;
};

struct MYStruct{
    double d;
    int i;
    char c;
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        union MYUnion u = {0};
        struct MYStruct s = {0};
        NSLog(@"联合体u内存大小:%lu", sizeof(u));
        NSLog(@"结构体s内存大小:%lu", sizeof(s));
        NSLog(@"MYUnion d = %f, c = %c, i = %d", u.d,u.c,u.i);
        NSLog(@"MYStruct d = %f, c = %c, i = %d", s.d,s.c,s.i);
        u.i = 1;
        s.i = 1;
        NSLog(@"MYUnion d = %f, c = %c, i = %d", u.d,u.c,u.i);
        NSLog(@"MYStruct d = %f, c = %c, i = %d", s.d,s.c,s.i);
        u.c = 'g';
        s.c = 'g';
        NSLog(@"MYUnion d = %f, c = %c, i = %d", u.d,u.c,u.i);
        NSLog(@"MYStruct d = %f, c = %c, i = %d", s.d,s.c,s.i);
        u.d = 9.9;
        s.d = 9.9;
        NSLog(@"MYUnion d = %f, c = %c, i = %d", u.d,u.c,u.i);
        NSLog(@"MYStruct d = %f, c = %c, i = %d", s.d,s.c,s.i);
    }
    return 0;
}

其中,在x86_64的Mac系统环境下:

  • double8个字节
  • int4个字节
  • char1个字节
    打印输出结果如下:

2020-09-11 11:51:17.505112+0800 PWDebug[60257:2817639] 联合体u内存大小:8
2020-09-11 11:51:17.506228+0800 PWDebug[60257:2817639] 结构体s内存大小:16
2020-09-11 11:51:17.506321+0800 PWDebug[60257:2817639] MYUnion d = 0.000000, c = , i = 0
2020-09-11 11:51:17.506392+0800 PWDebug[60257:2817639] MYStruct d = 0.000000, c = , i = 0
2020-09-11 11:51:17.506454+0800 PWDebug[60257:2817639] MYUnion d = 0.000000, c = �, i = 1
2020-09-11 11:51:17.506521+0800 PWDebug[60257:2817639] MYStruct d = 0.000000, c = , i = 1
2020-09-11 11:51:17.506579+0800 PWDebug[60257:2817639] MYUnion d = 0.000000, c = g, i = 103
2020-09-11 11:51:17.506639+0800 PWDebug[60257:2817639] MYStruct d = 0.000000, c = g, i = 1
2020-09-11 11:51:17.506696+0800 PWDebug[60257:2817639] MYUnion d = 9.900000, c = \315, i = -858993459
2020-09-11 11:51:17.506761+0800 PWDebug[60257:2817639] MYStruct d = 9.900000, c = g, i = 1

很明显看出,联合体u的其中一个成员(i,c,d)被修改后,其余的成员也受到影响。而结构体s的i,c,d三个变量是稳稳坐拥自己的内存空间,不受其他成员变量影响。

位域

什么是位域(概念)?

带有预定义宽度的变量被称为位域
有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态,用0和1表示足以,也就是用一个二进位。正是基于这种考虑,C语言又提供了一种数据结构,叫做“位域”或“位段”。

位域的声明

struct
{
  type [member_name] : width ;
};
元素 描述
type 只能为 int(整型),unsigned int(无符号整型),signed int(有符号整型) 三种类型,决定了如何解释位域的值。
member_name 位域的名称。
width 位域中位的数量。宽度必须小于或等于指定类型的位宽度,而且最小值为1。

下面我们来看一个例子:

struct
{
  unsigned int a;
  unsigned int w;
  unsigned int h;
} Person;
NSLog(@"%lu", sizeof(Person)); // =>  12
struct
{
  unsigned int a : 3;
  unsigned int w : 3;
  unsigned int h : 10;
} Teacher;
NSLog(@"%lu", sizeof(Teacher)); // =>  4
struct
{
  unsigned short a : 3;
  unsigned short w : 3;
  unsigned short h : 10;
} Student;
NSLog(@"%lu", sizeof(Student)); // =>  2

PersonTeacherStudent占用内存大小分别为1242,它们的成员占用内存如下图:

位域内存占用

总结:可想而知,在实际开发过程中,如果我们真的不需要那么多的内存空间的话,使用位域的编程方式会节省一大笔内存。

isa联合体位域

那么现在我们再回到isa的结构实现

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就是联合体位域,其中它有三个成员:clsbits和一个结构体

  • cls

    typedef struct objc_class *Class;
    

    Classobjc_class结构体指针类型,cls在64位架构下是占8字节。

  • bits

    #ifndef _UINTPTR_T
    #define _UINTPTR_T
    typedef unsigned long           uintptr_t;
    #endif /* _UINTPTR_T */
    

    bits 是一个无符号长整形,占 8 个字节。

  • 结构体

    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
    

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

    可知,是一个结构体位域:
    arm64架构下:1+1+1+33+6+1+1+1+19 = 64位,8字节。
    x86_64架构下:1+1+1+44+6+1+1+1+8 = 64位,也是8个字节。

所以,isa_t就只是占用了8字节的内存空间。
isa_t位域的解释图,图片来自(关联isa)

isa和类关联

未完待续...

你可能感兴趣的:(iOS 底层原理:isa结构分析)