淺析weak指針的實現

淺析weak指針的實現

oc中weak指針主要用於打破循環或者防止循環引用的發生,應用場景還是很廣泛的。那麼被weak修飾的指針與被指向的對象在底層的運作機制究竟怎樣的呢?為什麼在對象釋放銷燬時weak指針能自動置為nil,從而避免了野指針的錯誤?

weak指針實現原理

當對象被一個weak指針引用時,底層的實現原理就是:不對被引用的對象進行retain,而是利用哈希表對weak指針與被指向的對象進行標記、關聯。當對象銷燬釋放內存管時通過之前的標記對weak指針地址進行查找,最後把weak指針的指向置為nil。

weak指針實現源碼分析

首先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 referent; //聯合體,用於存放weak指針地址 union { struct { weak_referrer_t *referrers; //8字節 當inline_referrers不夠存放數據的時候,使用該指針在堆上開闢空間存放數據 uintptr_t out_of_line_ness : 2; uintptr_t num_refs : PTR_MINUS_2;//8字節 uintptr_t mask;//8字節 uintptr_t max_hash_displacement;//8字節 }; struct { // out_of_line_ness field is low bits of inline_referrers[1] weak_referrer_t inline_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

參考文獻:K碼農-http://kmanong.top/kmn/qxw/form/home?top_cate=28void 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。