前置文章 OC 对象 alloc 流程分析
本文研究 alloc 流程中重要的三个函数之一,initInstanceIsa
isa 的初始化
isa 初始化源码:
// 1.`_class_createInstanceFromZone` 函数调用
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
// 2. nonpointer 参数值为 true
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
// 3.存储 cls 信息
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
isa 介绍
// 宏定义
#define ISA_MASK 0x00007ffffffffff8ULL
#define ISA_MAGIC_MASK 0x001f800000000001ULL
#define ISA_MAGIC_VALUE 0x001d800000000001ULL
#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
#define RC_ONE (1ULL<<56)
#define RC_HALF (1ULL<<7)
// isa 联合体
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 中存储了一个 bits 属性,在 x86_64 平台,长度为 8 字节(占用 64 位)
这 64 位进行了固定分配,来存储指定的信息。
如下面的二进制数据,就是一个 isa.bits 内容,对应上图是从右到左
0b 00000000 0 0 0 111011 00000000000000000000000000000000000000000000 0 0 1
cls 存储到 isa
下面我们来看一下 cls 是怎么过存在 bits 中的
赋值前
我们把断点打在图中位置。输出如下信息
(lldb) p newisa
(isa_t) $3 = {
cls = 0x001d800000000001
bits = 8303511812964353
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 0
magic = 59
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
(lldb) p/t 8303511812964353 // 输出 bits 二进制
(long) $4 = 0b0000000000011101100000000000000000000000000000000000000000000001
(lldb) p/t (uintptr_t)cls // 输出 cls 指针二进制
(uintptr_t) $5 = 0b0000000000000000000000000000000100000000010001110100000000111000
(lldb) p/t (uintptr_t)cls >> 3 // 右移三位
(uintptr_t) $6 = 0b0000000000000000000000000000000000100000000010001110100000000111
可以看出,在未设置 shiftcls 时,bits 从右到左 [3, 46] 位都是0.
0b00000000000111011 00000000000000000000000000000000000000000000 001
为什么要右移三位?
在 Objective-C 中,类的指针是按照字节(8 bits)对齐的,也就是说类指针地址转化成十进制后,都是8的倍数,也就是说,类指针地址转化成二进制后,后三位都是0。既然是没有意义的0,那么在存储时就可以省略,用节省下来的空间存储一些其他信息。
赋值后
然后让断点往下走一步,如下图
输出信息如下:
(lldb) p newisa
(isa_t) $7 = {
cls = OS_dispatch_data
bits = 8303516112601145
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 537454599
magic = 59
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
(lldb) p/t 8303516112601145 // 将 bits 转为二进制
(long) $8 = 0b0000000000011101100000000000000100000000010001110100000000111001
可以看到,现在的 bits 的 [3, 46] 位正好是之前 cls 指针右移三位的内容 由于1和0宽度不同,因此没有对齐
赋值前: 0b00000000000111011 00000000000000000000000000000000000000000000 001
赋值后: 0b00000000000111011 00000000000000100000000010001110100000000111 001
转换过程如下图
至此,cls 信息已经存至 isa,其他 isa 信息的存储原理类似。
参考文章
Runtime中的 isa 结构体