在上一篇文章中,咱們從源碼上來看了下蘋果是如何實現一系列方法的,這篇文章將繼續分析 retainCount、retain 和 release 方法。c++
retainCount 是獲取當前對象引用計數的方法,其實現以下:less
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
複製代碼
直接調用了 rootRetainCount 方法,實現以下:ide
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this; // 若是是 tagged pointer,直接返回 this
sidetable_lock(); // 爲 sidetable 加鎖
isa_t bits = LoadExclusive(&isa.bits); // 經過 LoadExclusive 方法加載 isa 的值,加鎖
ClearExclusive(&isa.bits); // 解鎖
if (bits.nonpointer) { // 若是 isa 不是經過指針方式實現的話
uintptr_t rc = 1 + bits.extra_rc; // 獲取當前對象的引用計數
if (bits.has_sidetable_rc) { // 若是當前對象有額外的引用計數
rc += sidetable_getExtraRC_nolock(); // 加上額外的引用計數
}
sidetable_unlock(); // 爲 sidetable 解鎖
return rc;
}
sidetable_unlock(); // 爲 sidetable 解鎖
return sidetable_retainCount(); // 返回 sidetable 中存儲的 retainCount
}
複製代碼
rootRetainCount 方法作的事情已經寫到上面了,接下來來看一下其中幾個重要函數(方法)的實現:函數
LoadExclusive 的做用是讓讀取操做原子化,根據 CPU 不一樣實現不一樣,好比在 x86-64 上就是單純的直接返回值,而在 arm64 上則使用了 ldxr(load exclusive register) 指令。以下所示:ui
static ALWAYS_INLINE uintptr_t LoadExclusive(uintptr_t *src) {
uintptr_t result;
asm("ldxr %" p "0, [%x1]"
: "=r" (result)
: "r" (src), "m" (*src));
return result;
}
複製代碼
同理,ClearExclusive 則是解除鎖,使用了 clrex 指令,以下所示:this
static ALWAYS_INLINE void ClearExclusive(uintptr_t *dst) {
// pretend it writes to *dst for instruction ordering purposes
asm("clrex" : "=m" (*dst));
}
複製代碼
extra_rc 就是用於保存自動引用計數的標誌位,是在 isa_t 結構體中,共有8位。它存儲的是對象自己以外的引用計數的數量,因此獲取總數的時候要加 1。但 extra_rc 可能不足以存儲引用計數,這時候 sidetable 就派上用場了。spa
has_sidetable_rc 是用於標誌是否經過 sidetable 存儲引用計數的標誌。debug
sidetable_getExtraRC_nolock 函數是用於從 sidetable 中獲取引用計數信息的方法,實現以下:指針
size_t
objc_object::sidetable_getExtraRC_nolock()
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this]; // 根據 this 找到引用計數存在的 table
RefcountMap::iterator it = table.refcnts.find(this); // 查找引用計數
if (it == table.refcnts.end()) return 0; // 若是沒找到,返回0
else return it->second >> SIDE_TABLE_RC_SHIFT; // 若是找到了,經過 SIDE_TABLE_RC_SHIFT 位掩碼獲取對應的引用計數
}
複製代碼
若是 nonpointer 爲 true,表示使用的仍是老版本中經過指針表示 isa 的方式,這個時候,全部的引用計數所有存在 sidetable 中,以下所示:code
uintptr_t
objc_object::sidetable_retainCount()
{
SideTable& table = SideTables()[this]; // 根據 this 找到引用計數存在的 table
size_t refcnt_result = 1; // 設置對象自己的引用計數爲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; // 返回 1 + sidetable 中存儲的引用計數
}
table.unlock(); // 解鎖
return refcnt_result;
}
複製代碼
retain 的主要做用是增長引用計數,實現以下:
// Replaced by ObjectAlloc
- (id)retain {
return ((id)self)->rootRetain();
}
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; // 若是是 tagged pointer 直接返回 this
bool sideTableLocked = false; // 標記 sideTable 是否處於加鎖狀態
bool transcribeToSideTable = false; // 是否須要將引用計數存儲在 sideTable 中
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits); // 取出 isa,加鎖
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) { // 若是 isa 是經過指針方式實現的
ClearExclusive(&isa.bits); // 解鎖
if (!tryRetain && sideTableLocked) sidetable_unlock(); // 若是 tryRetain 爲 false 且給 sideTable 加過鎖,則爲其解鎖
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; // 若是 tryRetain 爲 ture,則根據調用 sidetable_tryRetain() 結果決定返回 this 或者是 nil。
else return sidetable_retain(); // 其他狀況直接返回 sidetable_retain 的結果
}
// don't check newisa.fast_rr; we already called any RR overrides
if (slowpath(tryRetain && newisa.deallocating)) { // 若是 tryRetain 爲 true,且對象正在被銷燬,則執行如下代碼
ClearExclusive(&isa.bits); // 解鎖
if (!tryRetain && sideTableLocked) sidetable_unlock(); // 若是 tryRetain 爲 false 且給 sideTable 加過鎖,則爲其解鎖
return nil;
}
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) { // 若是 carry 爲 true,要處理引用計數溢出的狀況
// newisa.extra_rc++ overflowed
if (!handleOverflow) { // 若是 handleOverflow 爲 false
ClearExclusive(&isa.bits); // 解鎖
return rootRetain_overflow(tryRetain); // 跳轉執行 rootRetain_overflow
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
if (!tryRetain && !sideTableLocked) sidetable_lock(); // 若是 sidetable_lock 未加鎖,則爲其加鎖
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true; // 標記是否在 sidetable 中存有引用計數
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) { // 若是須要講部分的引用計數放到 sidetable 中
// 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;
}
複製代碼
關於 retain 方法每一步作了什麼都已經寫到註釋裏了,接下來來看幾個知識點:
這兩個方法是成對兒使用的,做用是給 sidetable 加鎖/解鎖
void
objc_object::sidetable_lock()
{
SideTable& table = SideTables()[this];
table.lock();
}
void
objc_object::sidetable_unlock()
{
SideTable& table = SideTables()[this];
table.unlock();
}
複製代碼
sidetable_tryRetain 的實現以下:
bool
objc_object::sidetable_tryRetain()
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
// NO SPINLOCK HERE
// _objc_rootTryRetain() is called exclusively by _objc_loadWeak(),
// which already acquired the lock on our behalf.
// fixme can't do this efficiently with os_lock_handoff_s
// if (table.slock == 0) {
// _objc_fatal("Do not call -_tryRetain.");
// }
bool result = true;
RefcountMap::iterator it = table.refcnts.find(this); // 經過 this 從 table 中尋找是否有引用計數
if (it == table.refcnts.end()) { // 若是查找不到
table.refcnts[this] = SIDE_TABLE_RC_ONE; // 設置表中的引用計數爲 SIDE_TABLE_RC_ONE
} else if (it->second & SIDE_TABLE_DEALLOCATING) { // 若是對象此時處於 deallocating 狀態
result = false; // 結果設爲 false
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) { // 若是查找到了,且未溢出,則將引用計數加1。
it->second += SIDE_TABLE_RC_ONE;
}
return result;
}
複製代碼
Objecitve-C 定義了幾個重要的偏移量,用於取出對應的值,定義以下
// The order of these bits is important.
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING (1UL<<1) // MSB-ward of weak bit
#define SIDE_TABLE_RC_ONE (1UL<<2) // MSB-ward of deallocating bit
#define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1))
#define SIDE_TABLE_RC_SHIFT 2
#define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1)
複製代碼
實現以下:
id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { // 若是查找到了,且未溢出,則將引用計數加1。
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
複製代碼
這個函數的做用就是引用計數加一,其實現以下:
static ALWAYS_INLINE uintptr_t addc(uintptr_t lhs, uintptr_t rhs, uintptr_t carryin, uintptr_t *carryout) {
return __builtin_addcl(lhs, rhs, carryin, carryout);
}
複製代碼
__builtin_addcl 是 Clang Language Extensions,可是我沒找到它的定義,大概是大整數加法吧。
當 addc 方法告知咱們有溢出狀況出現時,咱們須要經過 rootRetain_overflow 方法處理溢出的狀況,實現以下:
NEVER_INLINE id
objc_object::rootRetain_overflow(bool tryRetain)
{
return rootRetain(tryRetain, true);
}
複製代碼
本質上屬於遞歸調用,又回到了這個方法。
NEVER_INLINE 的定義以下:
#define NEVER_INLINE inline __attribute__((noinline))
複製代碼
咱們在上一篇文章裏已經介紹過 inline 了,在此就不贅述了。
當咱們肯定引用計數已經溢出的時候,經過 newisa.extra_rc = RC_HALF; 將其設置爲溢出值。RC_HALF 定義以下:
#define RC_HALF (1ULL<<7)
複製代碼
extra_rc 總共爲 8 位,RC_HALF = 0b10000000。
其實現以下:
static ALWAYS_INLINE bool StoreExclusive(uintptr_t *dst, uintptr_t oldvalue, uintptr_t value) {
return __sync_bool_compare_and_swap((void **)dst, (void *)oldvalue, (void *)value);
}
複製代碼
__sync 開頭的方法是原子操做,這個函數是用來作比較後操做數的原子操做,若是 dst == oldvalue,就將 value 寫入 dst,並在相等並寫入的狀況下返回 true。
sidetable_addExtraRC_nolock 方法用於將溢出的引用計數加到 sidetable 中。實現以下:
// Move some retain counts to the side table from the isa field.
// Returns true if the object is now pinned.
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this];
size_t& refcntStorage = table.refcnts[this];
size_t oldRefcnt = refcntStorage;
// isa-side bits should not be set here
assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
uintptr_t carry;
size_t newRefcnt =
addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
if (carry) {
refcntStorage =
SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
return true;
}
else {
refcntStorage = newRefcnt;
return false;
}
}
複製代碼
在瞭解完 retain 的實現以後,release 的實現就比較容易解讀了,基本就是 retain 的反操做,實現以下:
-(void) release
{
_objc_rootRelease(self);
}
void
_objc_rootRelease(id obj)
{
assert(obj);
obj->rootRelease();
}
ALWAYS_INLINE bool
objc_object::rootRelease()
{
return rootRelease(true, false);
}
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
if (isTaggedPointer()) return false;
bool sideTableLocked = false; // 標識位,用於標記 sideTable 是否加鎖
isa_t oldisa;
isa_t newisa;
retry:
do {
oldisa = LoadExclusive(&isa.bits); // 爲 isa.bits 加鎖
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) { // 若是 newisa 是經過指針實現的
ClearExclusive(&isa.bits); // 爲 isa.bits 解鎖
if (sideTableLocked) sidetable_unlock(); // 若是 sideTableLocked 處於被加鎖狀態,爲其解鎖
return sidetable_release(performDealloc); // 經過 sidetable_release 爲其解鎖
}
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) { // 若是發現溢出的狀況
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock(); // 解鎖
return false;
underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// abandon newisa to undo the decrement
newisa = oldisa;
if (slowpath(newisa.has_sidetable_rc)) {
if (!handleUnderflow) {
ClearExclusive(&isa.bits); // 接觸 isa.bits 的鎖
return rootRelease_underflow(performDealloc); // 處理下溢的狀況
}
// Transfer retain count from side table to inline storage.
if (!sideTableLocked) {
ClearExclusive(&isa.bits); // 解除 isa.bits 的鎖
sidetable_lock(); // 爲 sidetable_lock 加鎖
sideTableLocked = true; // 標記 sideTable 被加鎖
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
goto retry;
}
// Try to remove some retain counts from the side table.
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF); // 將 sidetable 中的引用計數減一
// To avoid races, has_sidetable_rc must remain set
// even if the side table count is now zero.
if (borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
newisa.extra_rc = borrowed - 1; // redo the original decrement too
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits); // 存儲更改後的 isa.bits
if (!stored) { // 若是存儲失敗,即刻重試一次
// Inline update failed.
// Try it again right now. This prevents livelock on LL/SC
// architectures where the side table access itself may have
// dropped the reservation.
isa_t oldisa2 = LoadExclusive(&isa.bits);
isa_t newisa2 = oldisa2;
if (newisa2.nonpointer) {
uintptr_t overflow;
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
newisa2.bits);
}
}
}
if (!stored) { // 若是仍是更新失敗,則回滾操做並 retry
// Inline update failed.
// Put the retains back in the side table.
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
// Decrement successful after borrowing from side table.
// This decrement cannot be the deallocating decrement - the side
// table lock and has_sidetable_rc bit ensure that if everyone
// else tried to -release while we worked, the last one would block.
sidetable_unlock(); // 解鎖
return false;
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
// Really deallocate.
if (slowpath(newisa.deallocating)) { // 若是當前 newisa 處於 deallocating 狀態
ClearExclusive(&isa.bits); // 解除 isa.bits 的鎖
if (sideTableLocked) sidetable_unlock(); // 解除 sidetable 的鎖
return overrelease_error(); // 返回 overrelease_error 的結果值
// does not actually return
}
newisa.deallocating = true; // 設置 deallocating 爲 true
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; // 若是存儲失敗,繼續重試
if (slowpath(sideTableLocked)) sidetable_unlock(); // 解除 sidetable 的鎖
__sync_synchronize();
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return true;
}
複製代碼
sidetable_release 是爲 sidetable 中存儲的引用計數執行減1操做的方法,實現以下:
// rdar://20206767
// return uintptr_t instead of bool so that the various raw-isa
// -release paths all return zero in eax
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this]; // 經過對象獲取對應的 table
bool do_dealloc = false; // 標識是否須要執行 dealloc 方法
table.lock(); // 加鎖
RefcountMap::iterator it = table.refcnts.find(this); // 經過 this 查找引用計數
if (it == table.refcnts.end()) { // 未找到
do_dealloc = true;
table.refcnts[this] = SIDE_TABLE_DEALLOCATING; // 設爲 deallocating 狀態
} else if (it->second < SIDE_TABLE_DEALLOCATING) { // 若是對象處於 deallocating 狀態
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
it->second |= SIDE_TABLE_DEALLOCATING;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) { // 若是查找到了對象的引用計數
it->second -= SIDE_TABLE_RC_ONE; // 引用計數加一
}
table.unlock(); // 解鎖
if (do_dealloc && performDealloc) { // 若是符合調用 dealloc 方法的狀況下
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); // 經過 objc_msgSend 執行 dealloc 方法
}
return do_dealloc;
}
複製代碼
就是 addc 的反操做,實現以下:
static ALWAYS_INLINE uintptr_t subc(uintptr_t lhs, uintptr_t rhs, uintptr_t carryin, uintptr_t *carryout) {
return __builtin_subcl(lhs, rhs, carryin, carryout);
}
複製代碼
用於處理下溢的狀況,實現以下
NEVER_INLINE bool
objc_object::rootRelease_underflow(bool performDealloc)
{
return rootRelease(performDealloc, true);
}
複製代碼
sidetable_subExtraRC_nolock 用來將 sidetable 中存儲的引用計數減一,實現以下:
// Move some retain counts from the side table to the isa field.
// Returns the actual count subtracted, which may be less than the request.
size_t
objc_object::sidetable_subExtraRC_nolock(size_t delta_rc)
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end() || it->second == 0) {
// Side table retain count is zero. Can't borrow.
return 0;
}
size_t oldRefcnt = it->second;
// isa-side bits should not be set here
assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0); // 確保對象不是 deallocating 狀態
assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0); // 確保是否是弱飲用
size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT); // 引用計數減一
assert(oldRefcnt > newRefcnt); // shouldn't underflow
it->second = newRefcnt;
return delta_rc; // 返回引用計數的變化
}
複製代碼
StoreReleaseExclusive 內部直接調用了咱們以前提過的 StoreExclusive 方法,實現以下:
static ALWAYS_INLINE bool StoreReleaseExclusive(uintptr_t *dst, uintptr_t oldvalue, uintptr_t value) {
return StoreExclusive(dst, oldvalue, value);
}
複製代碼
overrelease_error 是用來在過分調用 release 的時候報錯用的,實現以下:
NEVER_INLINE
bool
objc_object::overrelease_error()
{
_objc_inform_now_and_on_crash("%s object %p overreleased while already deallocating; break on objc_overrelease_during_dealloc_error to debug", object_getClassName((id)this), this);
objc_overrelease_during_dealloc_error();
return false; // allow rootRelease() to tail-call this
}
BREAKPOINT_FUNCTION(
void objc_overrelease_during_dealloc_error(void)
);
複製代碼
__sync_synchronize 的做用是:
No memory operand will be moved across the operation, either forward or backward. Further, instructions will be issued as necessary to prevent the processor from speculating loads across the operation and from queuing stores after the operation.
簡而言之就是,在這個方法調用以後,涉及到內存的運算符都被禁止操做了(由於要進行析構了)。
以上就是關於 retainCount、retain 和 release 的所有源碼解讀了。