Weak與Weak表
The weak table is a hash table governed by a single spin lock.
An allocated blob of memory, most often an object, but under GC any such allocation, may have its address stored in a __weak marked storage location through use of compiler generated write-barriers or hand coded uses of the register weak primitive. Associated with the registration can be a callback block for the case when one of the allocated chunks of memory is reclaimed.
The table is hashed on the address of the allocated memory. When __weak marked memory changes its reference, we count on the fact that we can still see its previous reference.
So, in the hash table, indexed by the weakly referenced item, is a list of all locations where this address is currently being stored.
以上是objc4-781版本中objc_weak.h頭文件中對于weak表的定義。weak表是什么?回答這個問題我們需要聯想到開發(fā)中常用的weak或者__weak修飾詞。在開發(fā)中,通常在防止諸如delegate使用時的內存泄漏問題時,我們會在delegate屬性定義的修飾詞中使用weak來修飾它,這樣子就能解決使用代理時循環(huán)引用導致內存泄漏的問題?;蛘撸斘覀冇幸粋€UI控件在交互中顯示一次后就不再需要它時,我們也可以用weak來定義,這樣在它被從圖層上移除時就會自動置nil,減少了內存占用。但在這個機制的背后是怎樣運作的,為什么被weak或者__weak修飾的對象可以自動置nil,這背后就是weak表在起作用。
源碼定義
首先我們來看一下weak table在源碼中的定義。

可以看到
weak table的結構并不太復雜,結構體內有一個weak_entry_t類型的變量,該結構體是弱引用的入口,num_entries表示來表的大小,此外mask和max_hash_displacement兩個變量主要是與掩碼和哈希偏移量有關。通過源碼中的注釋可以知道,weak table以object ids作為hash table的keys,以weak_entry_t作為hash table的values。接下來我們看看weak_entry_t結構體是什么樣的。
其中,
DisguisedPtr的定義是這樣的

可以見到,DisguisedPtr類是對范型對象的指針做了一個封裝。weak_entry_t結構體其中的 referrers是weak_referrer_t的指針,也就是對DisguisedPtr<objc_object *>的二維指針,所以它是一個DisguisedPtr<objc_object *>的二維數組。通過一個二維指針地址偏移,用下標作為 hash 的 key,做成了一個弱引用散列。這就是weak table是hash table的來源,弱引用表是一個靜態(tài)的hash table結構。
weak對象插入
在NSObject.mm文件中我們可以找到StoreWeak方法的實現,StoreWeak方法由objc_initweak方法調用。

可以看到,在storeweak方法中,weak_register_no_lock方法是關鍵的將弱引用添加到weak table中的方法,當然在這之前還有各種必要的加鎖以及其他操作。
在weak_register_no_lock方法中,方法體里構建了弱引用的referent,其屬性是一個objc_object對象。

之后向referent中填充相關信息后,就要開始插入操作了。

可以看到,在這里首先定義了一個弱引用入口weak_entry_t *entry,并通過weak_entry_for_referent()方法嘗試獲取該對象的弱引用入口,如果獲取到了說明該對象在之前已經在weak table中存在了,那么就在weak_entry_t中拓展即可。
如果沒有獲取到,說明該弱引用對象是第一次被加入到weak table中,代碼執(zhí)行else分支,在創(chuàng)建了一個weak_entry_t后通過weak_entry_insert方法將其插入到全局的weak_table中。
至此,一個weak對象已經成功地加入到了weak table中。通過這個過程我們可以更清晰地了解到weak table運作的機制,也驗證了weak table是以object為key,weak_entry_t為value的hash table這一事實。
其他的相關方法

我們在
weak_table_t結構體的定義下可以看到objc_weak.h向外暴露了四個接口,分別是向weak table注冊插入對象、注銷移除對象、查找弱引用對象是否已在表中、將所有該object的弱引用指針置nil。這幾個方法是整個weak table運作的關鍵方法,當然還有諸如對weak table擴容等內部方法。
自此,__weak obj1 = obj;這行代碼的運行過程,已經全部梳理完成。
寫在最后
總體上來看,weak table的實現并不算太過復雜。它有著weak_table_t,weak_entry_t,DisguisedPtr<objc_object *>等幾個關鍵數據結構,有著weak_register_no_lock,weak_unregister_no_lock,weak_entry_insert,append_referrer等幾個關鍵方法。
其中更多的細節(jié),可以從objc4源碼中的objc_weak.h,objc_weak.mm,NSObject.h,NSObject.mm等文件獲取到。
如有錯漏,歡迎讀者提出。
Tino Wu