iOS内存(方案2) NONPOINTER_ISA 理解

1,367 阅读3分钟

NONPOINTER_ISA

这个设计思想跟TaggetPointer类似,ISA其实并不单单是一个指针。其中一些位仍旧编码指向对象的类。但是实际上并不会使用所有的地址空间,Objective-C 运行时会使用这些额外的位去存储每个对象数据就像它的引用计数和是否它已经被弱引用。
从源码来看:

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是一个定义的联合体。

什么是联合体?

当多个数据需要共享内存或者多个数据每次只取其一时,可以利用联合体(union),利用union可以用相同的存储空间存储不同型别的数据类型,从而节省内存空间。

我们来写一个联合体的例子

#define DirectionLeftMask (1 << 0)  // 0000 0001        //第一位表示向左
#define DirectionRightMask (1 << 0)  // 0000 0010       //第二位表示向右

@interface Car() {
    //联合体来表示方向
    union {
        char bits;
    }_direction;
}

@end

@implementation Car

- (instancetype)init
{
    self = [super init];
    if (self) {
        _direction.bits = 0b00000000;        //初始化二进制
    }
    return self;
}

- (void)moveLeft {
    _direction.bits |= DirectionLeftMask;  // 0000 0000 | 0000 0001 = 0000 0001
}

- (void)moveRight {
    _direction.bits |= DirectionRightMask; // 0000 0000 | 0000 0010 = 0000 0010
}

这样我们就用了同一片地址来表示了两个取值。
这样的写法有点麻烦,所以又引入了位域的概念。引入位域后如下代码:

@interface Car() {
    //联合体来表示4个方向
    union {
        char bits;
        struct {
            //位域
            char left : 1;    //第一位代表向前,占一位
            char right : 1;  
            char front : 1;
            char back : 1;
        };
    }_direction;
}

@end

@implementation Car

- (instancetype)init
{
    self = [super init];
    if (self) {
        _direction.bits = 0b00000000;        //初始化二进制
    }
    return self;
}

- (void)moveLeft {
    _direction.left = 1;
}

- (void)moveRight {
    _direction.right = 1;
}

- (void)moveFront {
    _direction.front = 1;
}

- (void)moveBack {
    _direction.back = 1;
}

接下来,我们接着查看isa的定义,它里面定义了一个位域:ISA_BITFIELD,点击查看这个宏:

# if __arm64__
#   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
# elif __x86_64__
#   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

可以看到,x86_64arm64下的位域定义是不一样的,不过都是占满了所有的64位(1+1+1+33+6+1+1+1+19 = 64,x86_64同理),下面来说明一下每一个位域参数的含义:

nonpointer:表示是否对isa开启指针优化 。0代表是纯isa指针,1代表除了地址外,还包含了类的一些信息、对象的引用计数等。
has_assoc:关联对象标志位
has_cxx_dtor:该对象是否有C++或Objc的析构器,如果有析构函数,则需要做一些析构的逻辑处理,如果没有,则可以更快的释放对象
shiftcls:存在类指针的值,开启指针优化的情况下,arm64位中有33位来存储类的指针
magic:判断当前对象是真的对象还是一段没有初始化的空间
weakly_referenced:是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象释放的更快
deallocating:是否正在释放
has_sidetable_rc:当对象引用计数大于10时,则需要进位
extra_rc:表示该对象的引用计数值,实际上是引用计数减一。例如:如果引用计数为10,那么extra_rc为9。如果引用计数大于10,则需要使用has_sidetable_rc