淺析weak指標的實現
oc中 weak
指標主要用於打破迴圈或者防止迴圈引用的發生,應用場景還是很廣泛的。那麼被 weak
修飾的指標與被指向的物件在底層的運作機制究竟怎樣的呢?為什麼在物件釋放銷燬時 weak
指標能自動置為 nil
,從而避免了野指標的錯誤?
weak指標實現原理
當物件被一個 weak
指標引用時,底層的實現原理就是:不對被引用的物件進行 retain
,而是利用雜湊表對 weak
指標與被指向的物件進行標記、關聯。當物件銷燬釋放記憶體管時通過之前的標記對 weak
指標地址進行查詢,最後把 weak
指標的指向置為 nil
。
weak指標實現原始碼分析
首先 weak
實現的函式呼叫順序如圖


關於 SideTable
以及根據物件指標查詢對應 SideTable
的分析在之前 分析物件引用計數的文章 中有相關的說明,這裡就不再重複了。這裡主要分析下處理 weak_entry_t
結構體的函式
id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, bool crashIfDeallocating) { // now remember it and where it is being stored weak_entry_t *entry; if ((entry = weak_entry_for_referent(weak_table, referent))) { //if 判斷 是否把weak指標指向的物件是否已經有對應的entry,有的話代表物件以前有被弱引用指向 append_referrer(entry, referrer); //把 referrer 存進 對應的 entry } else { //物件第一次被弱指標引用 weak_entry_t new_entry(referent, referrer); weak_grow_maybe(weak_table); //weak_table.weak_entries 擴容處理 weak_entry_insert(weak_table, &new_entry); //吧 weak_entry_t 放入weak_table.weak_entries中陣列,通過referent的雜湊運算&mask得到索引 } // Do not set *referrer. objc_storeWeak() requires that the // value not change. return referent_id; } 複製程式碼
這裡的邏輯主要是通過判斷查詢物件對應 SideTable
的 weak_table
屬性中是否包含有對應的 weak_entry_t
結構體,查詢的實現主要是通過把物件指標經過一定的雜湊演算法運算後與上一個特定的數值(該數值通常是要查詢順序表的最大索引)後得出的索引,再根據索引查詢到特定的值與要查詢的值對比得出結果。具體邏輯可以從原始碼中看 weak_entry_for_referent
這個函式的實現。
物件第一次被弱引用指向處理
上面的 else
語句就是處理物件第一次被弱引用指向的處理。通過生成一個 weak_entry_t
結構體後,把其插入到 weak_table
中,插入的邏輯與上面判斷是否含有特定 weak_entry_t
的邏輯都是一樣的,通過雜湊演算法獲得所以,然後插入相應的索引位置,這裡還有一個對 weak_table
進行擴容的處理 weak_grow_maybe(weak_table);
主要是判斷 weak_table
的存放物件容量大於或等於總容量的 3/4
時對 weak_table
的進行兩倍容量的擴容後,再把舊資料複製到擴容後的記憶體中。
物件之前已經被弱引用指向過
這種情況就相對複雜一點,首先我們看下最終存放 weak
指標地址的結構體定義
struct weak_entry_t { //被引用的物件指標(經過包裝處理) DisguisedPtr<objc_object> referent; //聯合體,用於存放weak指標地址 union { struct { weak_referrer_t *referrers; //8位元組 當inline_referrers不夠存放資料的時候,使用該指標在堆上開闢空間存放資料 uintptr_tout_of_line_ness : 2; uintptr_tnum_refs : PTR_MINUS_2;//8位元組 uintptr_tmask;//8位元組 uintptr_tmax_hash_displacement;//8位元組 }; struct { // out_of_line_ness field is low bits of inline_referrers[1] weak_referrer_tinline_referrers[WEAK_INLINE_COUNT]; // 32位元組 }; }; } 複製程式碼
weak_entry_t
結構體主要是通過上面的聯合體( union
)來存放物件的 weak
指標地址。程式碼中的聯合體分為兩部分,記憶體大小為64個位元組,在新增 weak
指標的時候優先使用的是 inline_referrers
陣列存放,如果陣列的已經存滿了資料(4個指標),就會用上面的結構體的陣列指標來在堆上開闢空間來存放資料,這樣就可以存放較多的 weak
指標地址。
weak指陣自動置nil原理
如果物件有被 weak
指標指向的話,在物件銷燬釋放記憶體執行 -dealloc
方法的時候,根據函式的呼叫順序會執行到 weak_clear_no_lock
這個方法。我們來看下次方法是怎樣使 weakPointer = nil
void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) { objc_object *referent = (objc_object *)referent_id; weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); weak_referrer_t *referrers; size_t count; if (entry->out_of_line()) { referrers = entry->referrers; count = TABLE_SIZE(entry); } else { referrers = entry->inline_referrers; count = WEAK_INLINE_COUNT; } for (size_t i = 0; i < count; ++i) { objc_object **referrer = referrers[i]; if (referrer) { if (*referrer == referent) { *referrer = nil; } } } } weak_entry_remove(weak_table, entry); } 複製程式碼
可以看出上面程式碼主要是獲取到對應物件的 weak_entry_t *entry
,根據其內部結構體獲取到存放的 weak
指標地址的個數,然後遍歷存放的 weak
指標的記憶體,把 weak
指標置為 nil
。