isa结构分析
在之前的文章OC对象的alloc过程中,我们探讨了OC对象初始化的主要过程,在第三步,主要是调用 initInstanceIsa方法,来初始化 isa指针 和关联类信息的,在这里,我们来共同探讨该过程。
分析
我们定义一个LYPerson类,并进行初始化。
int main(int argc, const char * argv[]) {
@autoreleasepool {
LYPerson *person = [LYPerson alloc];
}
return 0;
}
按照OC对象的alloc过程 中的过程方法,我们定位到 initInstanceIsa方法
// 3:
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
接下来我们就能定位到initIsa
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
我们看下initIsa函数的实现
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());
isa_t newisa(0);
.......
}
}
接下来我们可以看到isa_t是一个联合体,其结构如下
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
};
在x86架构下,其 位域如下
uintptr_t nonpointer : 1; // 1
uintptr_t has_assoc : 1; // 2
uintptr_t has_cxx_dtor : 1; // 3
uintptr_t shiftcls : 44; // 4 /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; // 5
uintptr_t weakly_referenced : 1; // 6
uintptr_t deallocating : 1; // 7
uintptr_t has_sidetable_rc : 1; // 8
uintptr_t extra_rc : 8 // 9
-
nonpointer,has_assoc,has_cxx_dtor各占1位。 -
shiftcls占44位。 -
magic占6位。 -
weakly_referenced、deallocating、has_sidetable_rc各占1位。 -
extra_rc占8位。
各个位表示什么含义呢????
关联类
首先我们先来探索 类是如何关联到isa的,在 initIsa方法中
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; // 1
- 1,将
cls进行了强制类型转换并右移3位存到shiftcls中
我们分析如下
(lldb) po cls // 1
LYPerson
(lldb) p/x cls
(Class) $10 = 0x0000000100002170 LYPerson
(lldb) p/x (uintptr_t)cls >> 3 // 2
(uintptr_t) $0 = 0x000000002000042e
(lldb) p/x newisa // 3
(isa_t) $3 = {
cls = 0x001d800100002171 LYPerson
bits = 0x001d800100002171
= {
nonpointer = 0x0000000000000001
has_assoc = 0x0000000000000000
has_cxx_dtor = 0x0000000000000000
shiftcls = 0x000000002000042e
magic = 0x000000000000003b
weakly_referenced = 0x0000000000000000
deallocating = 0x0000000000000000
has_sidetable_rc = 0x0000000000000000
extra_rc = 0x0000000000000000
}
}
(lldb) po 0x001d800100002171 & 0x00007ffffffffff8ULL // 4
LYPerson
(lldb) p/x 0x001d800100002171 >> 3 // 5
(long) $7 = 0x0003b0002000042e
(lldb) p/x 0x0003b0002000042e << 20
(long) $8 = 0x0002000042e00000
(lldb) p/x 0x0002000042e00000 >> 17
(long) $9 = 0x0000000100002170
(lldb) po 0x0000000100002170
LYPerson
- 1,在初始化
isa时,传入的cls为LYPerson - 2,将
cls进行类型转换,转换成uintptr_t并右移3位,将结果存入到shiftcls位置中,即联合体中的3 ~ 46中,这样就存储了类信息,进行了类关联。 - 3,将
isa指针进行打印 - 4,使用
ISA_MASK和isa指针来获取类名。我们将ISA_MASK(0x00007ffffffffff8ULL)转化为二进制格式
isa1.png
我们可以看出它就是
总长度为 64,中间44位值为 1,其余位数的值为0的二进制数,进行 与运算时,是取得中间44位的值。
- 5,从另一方面,取出
中间 44的值,将isa指针值右移3位,将前3位进行抹零,然后左移20位,将后17位进行抹零,最后,恢复中间的44位,得到值就为关联类。
小计:中间的shiftcls存储的信息为类的信息,
has_assoc
根据源码
inline void
objc_object::setHasAssociatedObjects()
{
if (isTaggedPointer()) return;
retry:
isa_t oldisa = LoadExclusive(&isa.bits);
isa_t newisa = oldisa;
if (!newisa.nonpointer || newisa.has_assoc) {
ClearExclusive(&isa.bits);
return;
}
newisa.has_assoc = true; // 1
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
- 1,
关联属性在赋值时,会讲has_assoc标志位改为true。
由于篇幅所限,对于其他标志位不在一一分析了,读者可自行探索。
总结
我们分析了isa的结构组成,它由联合体组成,里面有64位,它们分别代表了不同的含义。然后,我们又分析了isa关联类的实现和原理。下面我们在总结下每个标志位的作用。
1,nonpointer:表示是否对 isa 指针开启指针优化,0:纯isa指针,1:不止是类对象地址,isa中包含了类信息,对象的引用计数等。
2,has_assoc:关联对象标志位,0没有,1存在。
3,has_cxx_dtor: 该对象是否有 C++ 或者 Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。
4,shiftcls:存储类指针的值。
5,magic: 用于调试器判断当前对象是真的对象还是没有初始化的空间。
6,weak_referenced:该对象是否被指向或者曾经指向一个 ARC的弱变量,没有弱引用的对象可以更快释放。
7,deallocating:标志对象是否在释放内存。
8,hsa_sidetable_rc:当对象引用计数大于10时,则需要借助该变量存储进位。
9,extra_rc:当表示该对象的引用计数值,实际上是引用计数值减1.