深入理解isa指针
实例对象的isa指针指对象的类,类对象的isa指针指向对象的元类,这种说法对吗?
对,但也不完全对。因为在arm64架构之前,isa确实只存储着Class、Meta-Class对象的内存地址,但是在之后苹果对isa进行了优化,变成了一个共用体结构,使其能用位域存储更多的信息。而指向Class的指针需要按位与个ISA_MASK才可以获取到。
上图是一张很经典的指针指向图,虽然已经不完全正确,但是还是可以作为参考的。
上一篇文章提到,isa指针被优化成union isa_t类型,源码中搜索可以在objc-private.h文件中找到定义,去除方法简化后的代码如下
union isa_t {
uintptr_t bits;
private:
Class cls;
public:
struct {
ISA_BITFIELD; // defined in isa.h
};
};
然后再isa.h文件里面找到ISA_BITFIELD的定义,忽略x86和模拟器,把定义替换到以上代码中,可以变成如下
union isa_t {
uintptr_t bits;
private:
Class cls;
public:
struct {
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
};
};
那这些结构体里面的东西都表示什么意思呢?可以在源码里面搜索找下答案,我的源码版本是objc4-818.2,不同版本可能不太一样。
搜索isa.nonpointer; 会找到一个 hasNonpointerIsa()方法里面使用了,然后继续搜索hasNonpointerIsa(),会发现在objc-object.h文件里面有两个地方定义了这个函数,为什么会有两个呢,不会冲突吗?所以一定有条件编译指令区分编译。继续查找代码,会发现以下内容(简化代码)
#if SUPPORT_NONPOINTER_ISA //line 172
inline bool
objc_object::hasNonpointerIsa()
{
return isa.nonpointer;
}
#else //line 952
inline bool
objc_object::hasNonpointerIsa()
{
return false;
}
#endif //1217
对比下很明显可以看出,在SUPPORT_NONPOINTER_ISA部分,返回值应该为true,也就是当nonpointer为1时,表示优化过的指针。
搜索isa.has_assoc; 找到个hasAssociatedObjects()方法被调用,然后搜索has_assoc = 找到setHasAssociatedObjects()方法中被设置为true,但是并没有设置为false的地方。所以当这个值为1的时候,表示被设置过关联对象。
搜索isa.has_cxx_dtor; 找到hasCxxDtor(),然后搜索hasCxxDtor()方法在objc-private.h文件中找到以下注释
// object may have -.cxx_destruct implementation?
bool hasCxxDtor();
可以看出,这个值表示对象是否有C++的析构函数。苹果标识这些的作用是什么呢?
搜索hasCxxDtor();方法,发现objc-runtime-new.mm文件中如下代码
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
可以看出,在对象释放的时候,用于判断是否有关联对象和C++的析构函数,如果没有的话,可以更快的释放对象。
搜索shiftcls,并没有找到有用的信息。不过看名字猜测一定和类有关系,如果获取类指针的话,需要在现有的isa指针上按位与ISA_MASK,查看下ISA_MASK的值为0x0000000ffffffff8ULL,复制粘贴到计算器如图
看到值为1的位的长度正好和shiftcls的长度对应上,按照按位与的逻辑,保留值为1对应部分的值。可以确定shiftcls 存储的Class、Meta-Class对象的内存地址信息。
源码中搜索magic,并没有找到太多有用的信息,通过查询资料,得知这个值是为了在调试的时候分辨对象是否未完成初始化。
weakly_referenced 从字面意思就能看出,这个标识是否有弱引用。相同方法搜索得如下代码
// object may be weakly referenced?
bool isWeaklyReferenced();
搜索extra_rc; 会找到如下代码
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
if (bits.nonpointer) {
uintptr_t rc = bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
可以看到,extra_rc存储值为对象的引用计数,同时可以看到当has_sidetable_rc为1的时候,还需要加上另一个值才能得到准确的引用计数。
什么时候才会把引用计数写到sidetable中呢。搜索has_sidetable_rc = true 得到如下代码
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (variant != RRVariant::Full) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
发现newisa.extra_rc++ overflowed,当引用计数过大无法存入到extra_rc,会把引用计数写入到sidetable里面,并把has_sidetable_rc设置为true,extra_rc的值设置为RC_HALF,查看宏定义的值define RC_HALF (1ULL<<18),可得extra_rc最大存储引用计数为2^18 - 1 = 262143。
通过以上内容可以看出,优化后的指针可以存储很多内容,并且可以更有效率的获取存储的内容。