接着iOS alloc 底层原理继续进行后续的探索,在此之前需要先了解一下isa指针。
isa的结构分析
isa分为两种:普通的纯地址指针(cls)、被优化过的nonpointer指针。
相关内容:结构体&联合体&位域
isa_t是一个联合体,包含了cls与bits,其中cls就是普通的纯指针,而bits其实是被优化过的nonpointer指针,是一个结构体位域,记录了类地址以及其他信息。
union isa_t {
uintptr_t bits;对应与ISA_BITFIELD,
...
Class cls;
...,
};
isa内存分布
不同架构下,isa的分布也不相同。
arm64:
define ISA_MASK 0x0000000ffffffff8ULL //掩码,isa指针与之做&操作后才能获取到类(元类)对象的地址
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL //初始化isa时,用来设置magic与nonpointer
# define ISA_HAS_CXX_DTOR_BIT 1
# 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 unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45) //用来快速将指针平移到extra_rc位置
# define RC_HALF (1ULL<<18) //extra_r容量的一半,在将引用计数移到散列表时用到
x86_64:
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# 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 unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
为什么要用结构体位域?
以在arm64架构下为例,isa是64位,也就是8字节,如果使用结构体,那能操作的最小单位是字节,而nonpointer、has_assoc等只需要一位就可以存储,这样的话对内存会造成很大的浪费,所以在这里使用了结构体位域进行优化,能直接对每一位进行操作。
isa拆分
nonpointer:表示是否对 isa 指针开启指针优化 0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等。has_assoc:关联对象标志位,0没有,1存在。has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象。shiftcls:存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。magic:用于调试器判断当前对象是真的对象还是没有初始化的空间。weakly_referenced:对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。unused:以前是用来标志对象是否正在释放内存,现在变为了unused。has_sidetable_rc:该变量用来标记是否挂载了散列表,如果对象的引用计数达到了ertra_rc能存储的最大值。则会将ertra_rc容量一半(RC_HALF)的引用计数转移到散列表中存储,并且将has_sidetable_rc置为1。extra_rc:当表示该对象的引用计数值,实际值是引用计数值减 1,
ISA_MASK
以arm64架构为例,打印JLObject对象的内存,拿到开头的isa指针,并将isa指针使用po打印,发现竟然是一串数字,isa指向的不应该是类对象的地址吗。
我们再来使用ISA_MASK和isa做与操作,并将结果打印。
为什么会出现这种情况呢?
分析:isa前三位分别是nonpointer、has_assoc、has_cxx_dtor,从第四位开始,长度为33位是指向类/元类的地址shiftcls,而要拿到指向JLObject的指针,就需要只保留shiftcls,其他位清零。
ISA_MASK转换成二进制可以看到,对应shiftcls的33位都是1,其他位为0,按位与时,同为1得1,否则为0,以此来达到只保留shiftcls,其他位清零的目的。
我们还可以使用左移右移来达到清零的目的,有兴趣的小伙伴可以了解一下,这里就不进行具体的讲解了。
isa源码分析
在alloc三个重要步骤中,最后一步是初始化一个isa指针绑定到obj,下面是源码部分:
来看一下initInstanceIsa(Class cls, bool hasCxxDtor)
initIsa(Class cls)
发现它们两个内部都调用了initIsa(Class cls, bool nonpointer, bool hasCxxDtor)方法,
根据传入的参数可以断定initInstanceIsa用来生成nonpointer指针,而initIsa生成纯地址指针,在64位的环境下,只会执行initInstanceIsa。
/* nonpointer 标记是否为nonpointer指针
* hasCxxDtor 是否有C++或Objc的析构器
*/
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
//判断是不是小类型指针
ASSERT(!isTaggedPointer());
isa_t newisa(0);//创建一个isa(isa_t是一个联合体)
if (!nonpointer) {
newisa.setClass(cls, this);//如果nonpointer为false,则直接生成一个纯地址指针
} else {
......
newisa.bits = ISA_MAGIC_VALUE;//设置isa中的magic与nonpointer
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
newisa.extra_rc = 1;//引用计数设置为1
}
isa = newisa;
}
#define ISA_SIGNING_SIGN_NONE 1 // Sign no ISAs.
#define ISA_SIGNING_SIGN_ONLY_SWIFT 2 // Only sign ISAs of Swift objects.
#define ISA_SIGNING_SIGN_ALL 3 // Sign all ISAs.
通过断点可知,setClass中会执行isa.shiftcls = (uintptr_t)cls >> 3;且initInstanceIsa方法中传入的nonpointer为true,所以可以将代码简化一下:
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
isa_t newisa(0); //创建一个isa(isa_t是一个联合体)
newisa.bits = ISA_MAGIC_VALUE; //设置isa中的magic与nonpointer
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3 //newisa.setClass(cls, this);
newisa.extra_rc = 1;//引用计数设置为1
}
总结
通过以上代码可以清晰的看到,生成一个isa指针所需的步骤:
- 创建一个联合体类型的指针。
- 使用
ISA_MAGIC_VALUE为该指针的magic与nonpointer进行初始的赋值。 - 设置用来标识当前对象是否有C++ 或者 ObjC 的析构器的has_cxx_dtor。
- 将类对象地址右移三位,赋值到shiftcls。
- 引用计数设为1