關注倉庫,及時得到更新:iOS-Source-Code-Analyzegit
Follow: Draveness · Githubgithub
因爲 Objective-C 中的內存管理是一個比較大的話題,因此會分爲兩篇文章來對內存管理中的一些機制進行剖析,一部分分析自動釋放池以及
autorelease
方法,另外一部分分析retain
、release
方法的實現以及自動引用計數。ide
在接口設計時,咱們常常要考慮某些意義上的平衡。在內存管理中也是這樣,Objective-C 同時爲咱們提供了增長引用計數的 retain
和減小引用計數的 release
方法。this
這篇文章會在源代碼層面介紹 Objective-C 中 retain
和 release
的實現,它們是如何達到平衡的。spa
現在咱們已經進入了全面使用 ARC 的時代,幾年前還常用的 retain
和 release
方法已經很難出現於咱們的視野中了,絕大多數內存管理的實現細節都由編譯器代勞。設計
在這裏,咱們還要從 retain
方法開始,對內存管理的實現細節一探究竟。指針
下面是 retain
方法的調用棧:code
- [NSObject retain] └── id objc_object::rootRetain() └── id objc_object::rootRetain(bool tryRetain, bool handleOverflow) ├── uintptr_t LoadExclusive(uintptr_t *src) ├── uintptr_t addc(uintptr_t lhs, uintptr_t rhs, uintptr_t carryin, uintptr_t *carryout) ├── uintptr_t bits │ └── uintptr_t has_sidetable_rc ├── bool StoreExclusive(uintptr_t *dst, uintptr_t oldvalue, uintptr_t value) └── bool objc_object::sidetable_addExtraRC_nolock(size_t delta_rc) └── uintptr_t addc(uintptr_t lhs, uintptr_t rhs, uintptr_t carryin, uintptr_t *carryout)
調用棧中的前兩個方法的實現直接調用了下一個方法:
- (id)retain { return ((id)self)->rootRetain(); } id objc_object::rootRetain() { return rootRetain(false, false); }
而 id objc_object::rootRetain(bool tryRetain, bool handleOverflow)
方法是調用棧中最重要的方法,其原理就是將 isa
結構體中的 extra_rc
的值加一。
extra_rc
就是用於保存自動引用計數的標誌位,下面就是 isa
結構體中的結構:
接下來咱們會分三種狀況對 rootRetain
進行分析。
這是簡化後的 rootRetain
方法的實現,其中只有處理通常狀況的代碼:
id objc_object::rootRetain(bool tryRetain, bool handleOverflow) { isa_t oldisa; isa_t newisa; do { oldisa = LoadExclusive(&isa.bits); newisa = oldisa; uintptr_t carry; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); } while (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)); return (id)this; }
在這裏咱們假設的條件是
isa
中的extra_rc
的位數足以存儲retainCount
。
使用 LoadExclusive
加載 isa
的值
調用 addc(newisa.bits, RC_ONE, 0, &carry)
方法將 isa
的值加一
調用 StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)
更新 isa
的值
返回當前對象
在這裏調用 addc
方法爲 extra_rc
加一時,8 位的 extra_rc
可能不足以保存引用計數。
id objc_object::rootRetain(bool tryRetain, bool handleOverflow) { transcribeToSideTable = false; isa_t oldisa = LoadExclusive(&isa.bits); isa_t newisa = oldisa; uintptr_t carry; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); if (carry && !handleOverflow) return rootRetain_overflow(tryRetain); }
extra_rc
不足以保存引用計數,而且handleOverflow = false
。
當方法傳入的 handleOverflow = false
時(這也是一般狀況),咱們會調用 rootRetain_overflow
方法:
id objc_object::rootRetain_overflow(bool tryRetain) { return rootRetain(tryRetain, true); }
這個方法其實就是從新執行 rootRetain
方法,並傳入 handleOverflow = true
。
當傳入的 handleOverflow = true
時,咱們就會在 rootRetain
方法中處理引用計數的溢出。
id objc_object::rootRetain(bool tryRetain, bool handleOverflow) { bool sideTableLocked = false; isa_t oldisa; isa_t newisa; do { oldisa = LoadExclusive(&isa.bits); newisa = oldisa; uintptr_t carry; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); if (carry) { newisa.extra_rc = RC_HALF; newisa.has_sidetable_rc = true; } } while (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)); sidetable_addExtraRC_nolock(RC_HALF); return (id)this; }
當調用這個方法,而且 handleOverflow = true
時,咱們就能夠肯定 carry
必定是存在的了,
由於 extra_rc
已經溢出了,因此要更新它的值爲 RC_HALF
:
#define RC_HALF (1ULL<<7)
extra_rc
總共爲 8 位,RC_HALF = 0b10000000
。
而後設置 has_sidetable_rc
爲真,存儲新的 isa
的值以後,調用 sidetable_addExtraRC_nolock
方法。
bool objc_object::sidetable_addExtraRC_nolock(size_t delta_rc) { SideTable& table = SideTables()[this]; size_t& refcntStorage = table.refcnts[this]; size_t oldRefcnt = refcntStorage; 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; } }
這裏咱們將溢出的一位 RC_HALF
添加到 oldRefcnt
中,其中的各類 SIDE_TABLE
宏定義以下:
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0) #define SIDE_TABLE_DEALLOCATING (1UL<<1) #define SIDE_TABLE_RC_ONE (1UL<<2) #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)
由於 refcnts
中的 64 爲的最低兩位是有意義的標誌位,因此在使用 addc
時要將 delta_rc
左移兩位,得到一個新的引用計數 newRefcnt
。
若是這時出現了溢出,那麼就會撤銷此次的行爲。不然,會將新的引用計數存儲到 refcntStorage
指針中。
也就是說,在 iOS 的內存管理中,咱們使用了 isa
結構體中的 extra_rc
和 SideTable
來存儲某個對象的自動引用計數。
更重要的是,若是自動引用計數爲 1,extra_rc
實際上爲 0,由於它保存的是額外的引用計數,咱們經過這個行爲可以減小不少沒必要要的函數調用。
到目前爲止,咱們已經從頭梳理了 retain
方法的調用棧及其實現。下面要介紹的是在內存管理中,咱們是如何使用 release
方法平衡這個方法的。
與 release 方法類似,咱們看一下這個方法簡化後的調用棧:
- [NSObject release] └── id objc_object::rootRelease() └── id objc_object::rootRetain(bool performDealloc, bool handleUnderflow)
前面的兩個方法的實現和 retain
中的相差無幾,這裏就直接跳過了。
一樣,在分析 release
方法時,咱們也根據上下文的不一樣,將 release
方法的實現拆分爲三部分,說明它究竟是如何調用的。
這一個版本的方法調用能夠說是最簡版本的方法調用了:
bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) { isa_t oldisa; isa_t newisa; do { oldisa = LoadExclusive(&isa.bits); newisa = oldisa; uintptr_t carry; newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); } while (!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits)); return false; }
使用 LoadExclusive
獲取 isa
內容
將 isa
中的引用計數減一
調用 StoreReleaseExclusive
方法保存新的 isa
接下來,咱們就要看兩種相對比較複雜的狀況了,首先是從 SideTable
借位的版本:
bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) { isa_t oldisa; isa_t newisa; do { oldisa = LoadExclusive(&isa.bits); newisa = oldisa; uintptr_t carry; newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); if (carry) goto underflow; } while (!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits)); ... underflow: newisa = oldisa; if (newisa.has_sidetable_rc) { if (!handleUnderflow) { return rootRelease_underflow(performDealloc); } size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF); if (borrowed > 0) { newisa.extra_rc = borrowed - 1; bool stored = StoreExclusive(&isa.bits, oldisa.bits, newisa.bits); return false; } } }
這裏省去了使用鎖來防止競爭條件以及調用
StoreExclusive
失敗後恢復現場的代碼。
咱們會默認這裏存在SideTable
,也就是has_sidetable_rc = true
。
你能夠看到,這裏也有一個 handleUnderflow
,與 retain 中的相同,若是發生了 underflow
,會從新調用該 rootRelease
方法,並傳入 handleUnderflow = true
。
在調用 sidetable_subExtraRC_nolock
成功借位以後,咱們會從新設置 newisa
的值 newisa.extra_rc = borrowed - 1
並更新 isa
。
若是在 SideTable
中也沒有獲取到借位的話,就說明沒有任何的變量引用了當前對象(即 retainCount = 0
),就須要向它發送 dealloc
消息了。
bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) { isa_t oldisa; isa_t newisa; retry: do { oldisa = LoadExclusive(&isa.bits); newisa = oldisa; uintptr_t carry; newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); if (carry) goto underflow; } while (!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits)); ... underflow: newisa = oldisa; if (newisa.deallocating) { return overrelease_error(); } newisa.deallocating = true; StoreExclusive(&isa.bits, oldisa.bits, newisa.bits); if (performDealloc) { ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); } return true; }
上述代碼會直接調用 objc_msgSend
向當前對象發送 dealloc
消息。
不過爲了確保消息只會發送一次,咱們使用 deallocating
標記位。
在文章的最結尾,筆者想要介紹一下 retainCount
的值是怎麼計算的,咱們直接來看 retainCount
方法的實現:
- (NSUInteger)retainCount { return ((id)self)->rootRetainCount(); } inline uintptr_t objc_object::rootRetainCount() { isa_t bits = LoadExclusive(&isa.bits); uintptr_t rc = 1 + bits.extra_rc; if (bits.has_sidetable_rc) { rc += sidetable_getExtraRC_nolock(); } return rc; }
根據方法的實現,retainCount 有三部分組成:
1
extra_rc
中存儲的值
sidetable_getExtraRC_nolock
返回的值
這也就證實了咱們以前獲得的結論。
咱們在這篇文章中已經介紹了 retain
和 release
這一對用於內存管理的方法是如何實現的,這裏總結一下文章一下比較重要的問題。
extra_rc
只會保存額外的自動引用計數,對象實際的引用計數會在這個基礎上 +1
Objective-C 使用 isa
中的 extra_rc
和 SideTable
來存儲對象的引用計數
在對象的引用計數歸零時,會調用 dealloc
方法回收對象
有關於自動釋放池實現的介紹,能夠看自動釋放池的前世此生。
關注倉庫,及時得到更新:iOS-Source-Code-Analyze
Follow: Draveness · Github