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
的过程中要用到的,比如我们将isa
和ISA_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
的访问。