在64bit操作系统中,apple对对象中的isa进行了优化使用isa_t结构来保存关于对象的更多信息.
# 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
其中:extra_rc和has_sidetable_rc两个标志就是用来存储对象引用计数的.
引用计数的存储
引用计数的增加主要通过retain()函数来实现.
// Equivalent to calling [this retain], with shortcuts if there is no override
inline id
objc_object::retain()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
//在ARC中直接调用
return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
ALWAYS_INLINE id
objc_object::rootRetain()
{
return rootRetain(false, false);
}
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
//是否将引用计数结果转存到sidetable中
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//不支持nonpointer:引用计数保存在sidetable中
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
//不尝试增加引用计数且sidetable被锁,则打开sidetable锁定
if (!tryRetain && sideTableLocked) sidetable_unlock();
//尝试增加引用计数:如果尝试成功则返回当前对象;如果尝试失败则返回nil
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
//直接进行retain操作:在sidetable中使引用计数增加1
else return sidetable_retain();
}
//支持nonpointer
// 尝试增加引用计数且对象正在释放
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
//extra_rc溢出
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
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();
//sidetable加锁
sideTableLocked = true;
//引用计数转存到sidetable中
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
- 在不支持nonpointer的对象中,引用计数只存储在sidetable中;
- 可以看出在支持nonpointer的情况下:
- 在isa的位域extra_rc足以存储的情况,引用计数会优先选择存储在isa的extra_rc位域中;
- 若isa位域extra_rc溢出,则会选择将将引用计数的RC_HALF(如果extra_rc占据8bit,则RC_HALF=2^7)保存在isa中,另一半RC_HALF叠加保存在sidetable中.之所以这样选择是因为isa_t的存取理论上会比sidetable的操作效率上快很多,这样做既可以使超出extra_rc存储范围的引用计数得到有效存储,又可以确保引用计数的增减足够快速(存取都以extra_rc优先).
引用计数的获取
因为对象的引用计数主要存储在isa位域extra_rc和散列表中,所以要获取对象的引用计数也需要从两个位置进行获取.
uintptr_t
_objc_rootRetainCount(id obj)
{
assert(obj);
return obj->rootRetainCount();
}
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
//添加sidetable操作锁
sidetable_lock();
//加载对象isa
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
//如果对象开启了nonpointer
if (bits.nonpointer) {
//获取isa中存储的引用计数
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
//获取散列表中存储的引用计数
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
//对象未开启nonpointer
sidetable_unlock();
return sidetable_retainCount();
}
uintptr_t
objc_object::sidetable_retainCount()
{
SideTable& table = SideTables()[this];
size_t refcnt_result = 1;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
// this is valid for SIDE_TABLE_RC_PINNED too
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
return refcnt_result;
}
- 在开启nonpointer的对象中,对象的引用计数包括两部分:
- 存储在isa中的引用计数;
- 存储在sidetable中的引用计数.
- 在未开启nonpointer的对象中,对象的引用计数全部存储在sidetable中,只需要从sidetable中获取就可以.
这里有一个有意思的问题:
- 在开启nonpointer的对象中,引用计数retainCount=1+isa.extra_rc+sidetable_getExtraRC_nolock();
- 在未开启nonpointer的对象中,引用计数retainCount=1+table.refcnts.find(this)->second >> SIDE_TABLE_RC_SHIFT;
所以其实在创建对象的过程中(例如alloc和copy),存储的引用计数并没有进行+1操作,而是默认对象的最小引用计数为1.在release操作时才会判断引用计数-1是否为0,如果为0则进行释放;否则进行引用计数-1.