weak 弱引用的實現方式

發表於2016-09-16
 對於 runtime 的分析還有很長的路,最近在寫 block 系列的同時,也回顧一下之前疏漏的細節知識。這篇文章是關於 weak 的具體實現的學習筆記。

runtime 對 __weak 弱引用處理方式

切入主題,這裡筆者使用的 runtime 版本為 objc4-680.tar.gz。 我在入口檔案 main.m 中加入如下程式碼:

單步執行,發現會跳入 NSObject.mm 中的 objc_initWeak() 這個方法。在進行編譯過程前,clang 其實對 __weak 做了轉換,將宣告方式做出瞭如下調整。

其中的物件指標,就是程式碼中的 [[NSObject alloc] init] ,而 p 是我們傳入的一個弱引用指標。而對於 objc_initWeak() 方法的實現,在 runtime 中的原始碼如下:

可以看出,這個函式僅僅是一個深層函式的呼叫入口,而一般的入口函式中,都會做一些簡單的判斷(例如 objc_msgSend 中的快取判斷),這裡判斷了其指標指向的類物件是否有效,無效直接釋放,不再往深層呼叫函式。

需要注意的是,當修改弱引用的變數時,這個方法非執行緒安全。所以切記選擇競爭帶來的一些問題。

繼續閱讀 objc_storeWeak() 的實現:

其中標註的一些要點,開始逐一介紹:

引用計數和弱引用依賴表 SideTable

SideTable 這個結構體,我給他起名引用計數和弱引用依賴表,因為它主要用於管理物件的引用計數和 weak 表。在 NSObject.mm 中宣告其資料結構:

在之前的 runtime 版本中,有一個較為重要的成員方法,用來根據物件的地址在快取中取出對應的 SideTable 例項:

而在上面 objc_storeWeak 方法中,取出例項的方法變成了 &SideTables()[xxxObj]; 這種方式。檢視方法的實現,發現瞭如下函式:

在取出例項方法的實現中,使用了 C++ 標準轉換運算子 reinterpret_cast ,其表達方式為:

用來處理無關型別之間的轉換。該關鍵字會產生一個新值,並保證與原引數(expression)擁有完全相同的位元位

StripedMap 是一個模板類(Template Class),通過傳入類(結構體)引數,會動態修改在該類中的一個 array 成員儲存的元素型別,並且其中提供了一個針對於地址的 hash 演算法,用作儲存 key。可以說, StripedMap 提供了一套擁有將地址作為 key 的 hash table 解決方案,而該方案採用了模板類,是擁有泛型性的。

介紹了與物件相關聯的 SideTable 檢索方式,再來看 SideTable 的成員和作用。

對於 slock 和 refcnts 兩個成員不用多說,第一個是為了防止競爭選擇的自旋鎖,第二個是協助物件的 isa 指標的 extra_rc 共同引用計數的變數(對於物件結果,在今後的文中提到)。這裡主要看 weak 全域性 hash 表的結構與作用。

這是一個全域性弱引用表。使用不定型別物件的地址作為 key ,用 weak_entry_t 型別結構體物件作為 value 。其中的 weak_entries 成員,從字面意思上看,即為弱引用表入口。其實現也是這樣的。

在 weak_entry_t 的結構中,DisguisedPtr referent 是對泛型物件的指標做了一個封裝,通過這個泛型類來解決記憶體洩漏的問題。從註釋中寫 out_of_line 成員為最低有效位,當其為0的時候, weak_referrer_t 成員將擴充套件為多行靜態 hash table。其實其中的 weak_referrer_t 是二維 objc_object 的別名,通過一個二維指標地址偏移,用下標作為 hash 的 key,做成了一個弱引用雜湊。

那麼在有效位未生效的時候,out_of_linenum_refsmaskmax_hash_displacement 有什麼作用?以下是筆者自身的猜測:

  • out_of_line:最低有效位,也是標誌位。當標誌位 0 時,增加引用表指標緯度。
  • num_refs:引用數值。這裡記錄弱引用表中引用有效數字,因為弱引用表使用的是靜態 hash 結構,所以需要使用變數來記錄數目。
  • mask:計數輔助量。
  • max_hash_displacement:hash 元素上限閥值。

其實 out_of_line 的值通常情況下是等於零的,所以弱引用表總是一個 objc_objective 指標二維陣列。一維 objc_objective 指標可構成一張弱引用雜湊表,通過第三緯度實現了多張雜湊表,並且表數量為 WEAK_INLINE_COUNT 。

總結一下 StripedMap[]StripedMap 是一個模板類,在這個類中有一個 array 成員,用來儲存 PaddedT 物件,並且其中對於 [] 符的過載定義中,會返回這個 PaddedT 的 value 成員,這個 value 就是我們傳入的 T 泛型成員,也就是 SideTable 物件。在 array 的下標中,這裡使用了 indexForPointer 方法通過位運算計算下標,實現了靜態的 Hash Table。而在 weak_table 中,其成員 weak_entry 會將傳入物件的地址加以封裝起來,並且其中也有訪問全域性弱引用表的入口。

11sidetable

舊物件解除註冊操作 weak_unregister_no_lock

該方法主要作用是將舊物件在 weak_table 中接觸 weak 指標的對應繫結。根據函式名,稱之為解除註冊操作。從原始碼中,可以知道其功能就是從 weak_table 中接觸 weak 指標的繫結。而其中的遍歷查詢,就是針對於 weak_entry 中的多張弱引用雜湊表。

新物件新增註冊操作 weak_register_no_lock

這一步與上一步相反,通過 weak_register_no_lock 函式把心的物件進行註冊操作,完成與對應的弱引用表進行繫結操作。

初始化弱引用物件流程一覽

弱引用的初始化,從上文的分析中可以看出,主要的操作部分就在弱引用表的取鍵、查詢雜湊、建立弱引用表等操作,可以總結出如下的流程圖:

11weaktable-2

這個圖中省略了很多情況的判斷,但是當宣告一個 __weak 會呼叫上圖中的這些方法。當然, storeWeak 方法不僅僅用在 __weak 的宣告中,在 class 內部的操作中也會常常通過該方法來對 weak 物件進行操作。

以上就是對於 weak 弱引用物件的初始化時 runtime 內部的執行過程,想必閱讀後會對其結構有更深的理解。

相關文章