oc中weak
指針主要用於打破循環或者防止循環引用的發生,應用場景仍是很普遍的。那麼被weak
修飾的指針與被指向的對象在底層的運做機制究竟怎樣的呢?爲何在對象釋放銷燬時weak
指針能自動置爲nil
,從而避免了野指針的錯誤?算法
當對象被一個weak
指針引用時,底層的實現原理就是:不對被引用的對象進行retain
,而是利用哈希表對weak
指針與被指向的對象進行標記、關聯。當對象銷燬釋放內存管時經過以前的標記對weak
指針地址進行查找,最後把weak
指針的指向置爲nil
。數組
首先weak
實現的函數調用順序如圖 bash
weak
指針標記須要處理的對象順序以下圖所示:
關於SideTable
以及根據對象指針查找對應SideTable
的分析在以前分析對象引用計數的文章中有相關的說明,這裏就再也不重複了。這裏主要分析下處理weak_entry_t
結構體的函數app
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
這個函數的實現。ide
上面的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_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
指針地址。post
若是對象有被weak
指針指向的話,在對象銷燬釋放內存執行-dealloc
方法的時候,根據函數的調用順序會執行到weak_clear_no_lock
這個方法。咱們來看下次方法是怎樣使weakPointer = nil
ui
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
。spa