OC的isa指针存储了哪些信息?

OC中所有的实例对象、类对象和元类对象中都一个名为isa的成员变量,他们通常把它叫isa指针,既然是指针,那里面存储的应该就是一个地址。在以前的32位系统中,isa确实就是存储的一个地址,实例对象的isa存储的是其对应的类对象的地址,类对象的isa存储的是其对应的元类对象的地址,元类对象的isa存储的是根元类对象的地址。

但是在现在的64位系统(arm64架构)中,苹果对isa做了优化,里面除了存储一个地址外还存储了很多其他信息。一个指针占8个字节,也就是64位,苹果只用了其中的33位来存储地址,其余31位用来存储其他信息。下面我们来看下在arm64架构中关于isa的定义:

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

上面信息中定义的像ISA_MASK这种常量我们不用管,这些都是程序在操作isa的过程中要用到的,比如我们将isaISA_MASK进行按位与运算isa & ISA_MASK就可以得到isa中存储的地址值。

我们主要关注一下uintptr_t类型数据:

  • nonpointer:(isa的第0位(isa的最后面那位),共占1位)。为0表示这个isa只存储了地址值,为1表示这是一个优化过的isa
  • has_assoc:(isa的第1位,共占1位)。记录这个对象是否是关联对象,没有的话,释放更快。
  • has_cxx_dtor:(isa的第2位,共占1位)。记录是否有c++的析构函数,没有的话,释放更快。
  • shiftcls:(isa的第3-35位,共占33位)。记录类对象或元类对象的地址值。
  • magic:(isa的第36-41位,共占6位),用于在调试时分辨对象是否完成初始化。
  • weakly_referenced:(isa的第42位,共占1位),用于记录该对象是否被弱引用或曾经被弱引用过,没有被弱引用过的对象可以更快释放。
  • deallocating:(isa的第43位,共占1位),标志对象是否正在释放内存。
  • has_sidetable_rc:(isa的第44位,共占1位),用于标记是否有扩展的引用计数。当一个对象的引用计数比较少时,其引用计数就记录在isa中,当引用计数大于某个值时就会采用sideTable来协助存储引用计数。
  • extra_rc:(isa的第45-63位,共占19位),用来记录该对象的引用计数值-1(比如引用计数是5的话这里记录的就是4)。这里总共是19位,如果引用计数很大,19位存不下的话就会采用sideTable来协助存储,规则如下:当19位存满时,会将19位的一半(也就是上面定义的RC_HALF)存入sideTable中,如果此时引用计数又+1,那么是加在extra_rc上,当extra_rc又存满时,继续拿出RC_HALF的大小放入sideTable。当引用计数减少时,如果extra_rc的值减少到了0,那就从sideTable中取出RC_HALF大小放入extra_rc中。综上所述,引用计数不管是增加还是减少都是在extra_rc上进行的,而不会直接去操作sideTable,这是因为sideTable中有个自旋锁,而引用计数的增加和减少操作是非常频繁的,如果直接去操作sideTable会非常影响性能,所以这样设计来尽量减少对sideTable的访问。

推荐阅读更多精彩内容