1. isa介绍
在arm64
架构之前,isa
就是一个普通的指针,存储着Class、Meta-Class
对象的内存地址。
从arm64
架构开始,对isa
进行了优化,变成了一个共用体(union
)结构,还使用位域
来存储更多的信息,不只包含类或者元类信息,还包含是否有弱引用
、是否有关联对象
等信息,这样在对象释放的时候,才能将弱引用和关联对象一起释放,所以我们拿到isa
之后还需要进行一次位运算(&ISA_MASK
)才能计算出Class
或者Meta-Class
的真实地址,如下图所示:
在了解为什么要进行一次位运算
(&ISA_MASK)
才能计算出Class
或者Meta-Class
的真实地址之前,我们需要先了解一下isa
的源码。
2. isa源码
objc
中isa
源码:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {
ISA_BITFIELD; // defined in isa.h
};
};
在runtime723
版本以前,直接把结构体放在isa
里面了。750
版本之后,抽成宏了,如下所示:
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# 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 deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# 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)
# else
# error unknown architecture for packed isa
# endif
下面的代码对isa_t
中的结构体进行了位域
声明,位域
也是对结构体内存布局进行了一个声明,通过下面的结构体成员变量可以直接操作某个地址。位域总共占8字节
,所有的位域加在一起正好是64
位:
define ISA_BITFIELD \
uintptr_t nonpointer : 1; //指针是否优化过 \
uintptr_t has_assoc : 1; //是否有设置过关联对象,如果没有,释放时会更快 \
uintptr_t has_cxx_dtor : 1; //是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快 \
uintptr_t shiftcls : 33; //存储着Class、Meta-Class对象的内存地址信息 \
uintptr_t magic : 6; //用于在调试时分辨对象是否未完成初始化 \
uintptr_t weakly_referenced : 1; //是否有被弱引用指向过,如果没有,释放时会更快 \
uintptr_t deallocating : 1; //对象是否正在释放 \
uintptr_t has_sidetable_rc : 1; //引用计数器是否过大无法存储在isa中 \
uintptr_t extra_rc : 19 //里面存储的值是引用计数器减1
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
isa
中不同的位域代表不同的含义。
2.1 nonpointer
- 值为0,代表这是普通的指针,只存储着
Class、Meta-Class
对象的内存地址; - 值为1,代表isa被优化过,使用位域存储更多的信息。
这个我们在阅读源码时,经常会遇到这个判断,我们以初始化isa
的方法为例,去探究一下,如下:
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());
// 如果nonpointer为false,直接使用cls地址初始化isa
if (!nonpointer) {
isa = isa_t((uintptr_t)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
// # define ISA_MAGIC_VALUE 0x001d800000000001ULL
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
...
}
从上面方法中可以看到,传递的第二个参数nonpointer
为true
,在下面initIsa
方法中设置isa
的初始值:newisa.bits = ISA_MAGIC_VALUE
,ISA_MAGIC_VALUE
值为0x001d800000000001ULL
,转换为二进制之后末位为1,说明newisa. nonpointer
为1,标明优化过,使用位域存储更多的信息。
2.2 has_assoc
- 是否有设置过关联对象,如果没有,释放时会更快(在对象
dealloc
时,会通过这个标志判断是否有关联对象,如果有,则会释放关联的对象
)
这一点可以在研究Category
原理时看到,给对象设置了关联对象之后,需要将对象的isa
的has_assoc
设置为true
,相关的代码如下:
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;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
2.3 has_cxx_dtor
- 是否有C++的析构函数(
.cxx_destruct
),如果没有,释放时会更快
2.4 shiftcls
- 存储着
Class、Meta-Class
的内存地址信息
我们可以先看下获取对象所属类的方法,如下:
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
return ISA();
}
最终会调用到ISA()
方法里面,代码如下:
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
}
可以看到最终返回的值是(Class)(isa.bits & ISA_MASK);
,也验证了刚开始我们所提到的拿到isa之后还需要进行一次位运算(&ISA_MASK)
才能计算出Class
或者Meta-Class
的真实地址。
ISA_MASK
其实是个宏定义:
# define ISA_MASK 0x00007ffffffffff8ULL
// 对应的二进制值为:
0b0000000000000000011111111111111111111111111111111111111111111000
可以看到中间有33位为1,isa.bits & ISA_MASK
就相当于将前28位和后3位置为0
,只留下中间33位不变
,这样获取的也就是对象所属Class
的内存地址。
shiftcls
赋值是在初始化isa
时进行的,在上面介绍初始化isa
方法时有看到对shiftcls
赋值:newisa.shiftcls = (uintptr_t)cls >> 3;
,也就是将对象所属类的地址向右移3位
(在取值进行& ISA_MASK
时又补足这3位了),赋值给newisa.shiftcls
。
2.5 magic
- 用于在调试时分辨对象是否未完成初始化
2.6 weakly_referenced
- 是否有被弱引用指向过,如果没有,释放时会更快
在介绍weak实现
流程的文章中有提到过,设置的代码如下:
inline void
objc_object::setWeaklyReferenced_nolock()
{
retry:
isa_t oldisa = LoadExclusive(&isa.bits);
isa_t newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
sidetable_setWeaklyReferenced_nolock();
return;
}
if (newisa.weakly_referenced) {
ClearExclusive(&isa.bits);
return;
}
newisa.weakly_referenced = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
2.7 deallocating
- 标志对象是否正在释放
可以在release
方法的源码中看到,将deallocating
设置为true
之后,给该对象发送了dealloc
消息,如下:
...
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
...
}
2.8 extra_rc
- 里面存储的值是对象的引用计数减1
2.9 has_sidetable_rc
- 标记引用计数器是否过大无法存储在isa中,如果为1,那么引用计数会存储在一个叫SideTable的类的中