在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.