如果对NSObject
的实现有过研究,应该就知道,所有的对象,不管是实例对象,还是类对象,其实质都是一个C语言结构体
:
1 | typedef struct objc_class *Class; |
从以上结构体的定义,可以很清楚的看出,不管是实例对象还是类对象,其结构内的首个地址存储的就是isa指针,而isa指针的类型又为Class
,即 struct objc_class
,所以如果想对isa有一个更深入的认识,就需要对objc_class
这个结构体进一步分析。
注:
1 | struct objc_object { |
这个结构体里面的第一个虽然不是struct objc_class
指针,但是细看isa_t
的定义,实际上它还是返回的一个struct objc_class
的指针:
1 | #include "isa.h" |
这部分是apple对isa的优化。自从引入tagged pointer
之后,isa就再也不是单纯的指针的,所以有时候直接查看对象内的isa时,会发现他根本就不是一个指针:
可以看出,取出对象c中第一个地址的值,是0x000001a1000f133d
,显然它不符合一个指针,因为在arm64下,指针是8字节对齐的,也就是说,指针最后一位,要么是0x0,要么是0x8,还有就是,/*MACH_VM_MAX_ADDRESS 0x1000000000*/
,但是显然0x000001a1000f133d
比0x1000000000
大的多。
那么问题就来了:对于被优化的isa,我们该怎么去获取真正的isa指针呢?
要回答这个问题,我们要先了解下object_getClass(id _Nullable obj)
的实现:
1 | Class object_getClass(id obj) |
在runtime
源码中找到ISA()
:
1 | inline Class |
对于arm64,可以简化为:
1 | inline Class |
这个isa.bits
的值,在上例中为:0x000001a1000f133d,而ISA_MASK的定义为:
1 | # define ISA_MASK 0x0000000ffffffff8ULL |
仔细观察这个0x0000000ffffffff8ULL
,你会发现,它恰好在第3
bit到第35
bit的值为1,而&上isa.bits的意思是取isa.bits的3
bit~`35bit的值,结合刚刚给出的
isa_t`的定义:
1 | # define ISA_BITFIELD \ |
正好对上了shiftcls
,似乎一切都是巧合,但其实不是,早在对象初始化的时候,就已经将isa指针的值,存入shiftcls
了:
1 | newisa.bits = ISA_MAGIC_VALUE; |
其他bit的含义:
- has_assoc
对象含有或者曾经含有关联引用,没有关联引用的可以更快地释放内存 - has_cxx_dtor
当前对象有 C++ 或者 ObjC 的析构器(destructor),如果没有析构器就会快速释放内存。 - magic
用于调试器判断当前对象是真的对象还是没有初始化的空间 - weakly_referenced
对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放 - deallocating
对象正在释放内存 - has_sidetable_rc
对象的引用计数太大了,存不下 - extra_rc
对象的引用计数超过 1,会存在这个这个里面,如果引用计数为 10,extra_rc 的值就为 9