在探索alloc流程的那一篇文章中,我们留下了一个initInstanceIsa()没有探索,这次我们就来全面的探索一下isa。
isa的结构分析
首先我们先跟源码,可以看到initInstanceIsa()的源码:
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.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
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;
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可以发现:

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_t结构我们可以得知isa其实一个联合体,联合体中各成员共享内存,联合体内存的大小取决于内部成员内存最大的那个。可以看到在联合体中还存在一个结构体,点进去查看:

这里要补充一下位域相关的信息:
信息的存取一般以字节为单位。实际上,有时存储一个信息不必用一个或多个字节,例如,“真”或“假”用0或1表示,只需1位即可。在计算机用于过程控制、参数检测或数据通信领域时,控制信息往往只占一个字节中的一个或几个二进制位,常常在一个字节中放几个信息。
在C语言中,位段(位域)的声明和结构(struct)类似,但它的成员是一个或多个位的字段,这些不同长度的字段实际储存在一个或多个整型变量中。在声明时,位段成员必须是整形或枚举类型(通常是无符号类型),且在成员名的后面是一个冒号和一个整数,整数规定了成员所占用的位数。位域不能是静态类型。不能使用&对位域做取地址运算,因此不存在位域的指针,编译器通常不支持位域的引用(reference)。
知道了位域的概念,我们再来看ISA_BITFIELD每一位代表的具体含义:
nonpointer :1 表示是否对isa指针开启指针优化;0代表纯isa指针,1代表不止是类对象指针,还包含了类信息、对象的引用计数等;
has_assoc : 1 关联对象标志位,0没有,1存在;
has_cxx_dtor :1 该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象;
shiftcls :33 存储类指针的值。开启指针优化的情况下,在arm64架构中有33位用来存储类指针;
magic :6 用于调试器判断当前对象是真的对象还是没有初始化的空间;
weakly_referenced :1 标志对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放;
deallocating :1 标志对象是否正在释放内存;
has_sidetable_rc :1 当对象引用计数大于10时,则需要借用该变量存储进位
extra_rc :19 当表示该对象的引用计数值,实际上是引用计数值减1,例如,如果对象的引用计数为10,那么extra_rc为9.如果引用计数大于10,则需要使用上面提到的has_sidetable_rc。
将所有相加可得64位,即8字节,再结合联合体中其他成员可知isa为8字节,而联合体和位域的配合使用也使得isa不用添加过多的属性去存储信息,节省了很多空间。
isa的指向分析
1.对象与类的关联
我们了解了isa的结构,那么isa是如何将对象与类关联起来的呢?我们可以通过api objc_getClass来查看:
DZPerson *person = [DZPerson alloc];
object_getClass(person);
查看源码并跟进:
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
if (!isTaggedPointer()) return ISA();
uintptr_t ptr = (uintptr_t)this;
if (isExtTaggedPointer()) {
uintptr_t slot =
(ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
return objc_tag_ext_classes[slot];
} else {
uintptr_t slot =
(ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
}
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
可以看到核心代码:
return (Class)(isa.bits & ISA_MASK);
由此可以得知将isa与ISA_MASK进行位运算后得到的就是类,也就证明了是isa将对象与类关联起来的。
isa的走向
我们可以通过lldb来查看isa的走向:
(lldb) x/4xg person
0x101a74ce0: 0x001d8001000011c9 0x0000000000000000
0x101a74cf0: 0x0000000101a74dc0 0x0000000101a75000
(lldb) p/x 0x001d8001000011c9 & 0x00007ffffffffff8
(long) $1 = 0x00000001000011c8
(lldb) po $1
DZPerson (类)
(lldb) x/4xg $1
0x1000011c8: 0x001d8001000011a1 0x0000000100aff140
0x1000011d8: 0x00000001003a2260 0x0000000000000000
(lldb) p/x 0x001d8001000011a1 & 0x00007ffffffffff8
(long) $2 = 0x00000001000011a0
(lldb) po $2
DZPerson (元类)
(lldb) x/4xg $2
0x1000011a0: 0x001d800100aff0f1 0x0000000100aff0f0
0x1000011b0: 0x0000000101a72fc0 0x0000000100000007
(lldb) p/x 0x001d800100aff0f1 & 0x00007ffffffffff8
(long) $3 = 0x0000000100aff0f0
(lldb) po $3
NSObject (根元类)
(lldb) x/4xg $3
0x100aff0f0: 0x001d800100aff0f1 0x0000000100aff140
0x100aff100: 0x0000000101a73080 0x0000000300000007
(lldb) p/x 0x001d800100aff0f1 & 0x00007ffffffffff8
(long) $4 = 0x0000000100aff0f0
(lldb) po $4
NSObject (根元类)
由此可以得出isa的指向关系:
对象 -> 类 -> 元类 -> 根元类 -> 根元类(根元类的isa指向自己)
这时就需要官方给出的isa走位图:

到这里我们就分析完了isa的结构和走位指向,笔者能力有限,如有错误请在评论区指出。