今年年底做了很多決定,離開工作三年的深圳,來到了上海,發現深圳和上海在蘋果這方面還是差距有點大的,上海的市場8成使用swift編程,而深圳8成的使用OC,這點還是比較讓准備來上海打拼的蘋果工程師有點小壓力的。畢竟以后蘋果還是swift使用的多,現在已經swift4.x了,所以早點接觸,還是有優勢的,不過。咱們閑話少說,今天我們將繼續講述OC修飾屬性的一個Weak修飾符的底層實現,有時間我會花時間講述swift4.x。
一、weak基本用法
weak是弱引用,用weak來修飾、描述所引用對象的計數器並不會加1,而且weak會在引用對象被釋放的時候自動置為nil,這也就避免了野指針訪問壞內存而引起奔潰的情況,另外weak也可以解決循環引用。
拓展:為什么修飾代理使用weak而不是用assign?
assign可用來修飾基本數據類型,也可修飾OC的對象,但如果用assign修飾對象類型指向的是一個強指針,當指向的這個指針釋放之后,它仍指向這塊內存,必須要手動給置為nil,否則會產生野指針,如果還通過此指針操作那塊內存,會導致EXC_BAD_ACCESS錯誤,調用了已經被釋放的內存空間;而weak只能用來修飾OC對象,而且相比assign比較安全,如果指向的對象消失了,那么它會自動置為nil,不會導致野指針。
二、weak原理概括
weak表其實是一個哈希表,key是所指對象的指針,value是weak指針的地址數組。(value是數組的原因是:因為一個對象可能被多個弱引用指針指向)
Runtime維護了一張weak表,用來存儲某個對象的所有的weak指針。
weak原理實現過程三步驟
- 初始化開始時,會調用objc_initWeak函數,初始化新的weak指針指向對象的地址
2.緊接着,objc_initWeak函數里面會調用objc_storeWeak() 函數,objc_storeWeak() 函數的作用是用來更新指針的指向,創建弱引用表。
3.在最后會調用clearDeallocating函數。而clearDeallocating函數首先根據對象的地址獲取weak指針地址的數組,然后緊接着遍歷這個數組,將其中的數組開始置為nil,把這個entry從weak表中刪除,最后一步清理對象的記錄。
拓展:詳細步驟
- 初始化開始時,會調用objc_initWeak函數,初始化新的weak指針指向對象的地址
當我們初始化weak變量時,runtime會調用NSObject.mm中的objc_initWeak,而objc_initWeak函數里面的實現如下:
id objc_initWeak(id *location, id newObj) { // 查看對象實例是否有效,無效對象直接導致指針釋放 if (!newObj) { *location = nil; return nil; } // 這里傳遞了三個 bool 數值 // 使用 template 進行常量參數傳遞是為了優化性能 return storeWeakfalse/*old*/, true/*new*/, true/*crash*/> (location, (objc_object*)newObj); }
通過上面代碼可以看出,objc_initWeak()函數首先判斷指針指向的類對象是否有效,無效,直接返回;否則通過storeWeak()被注冊為一個指向value的_weak對象
2. objc_initWeak函數里面會調用objc_storeWeak() 函數,objc_storeWeak() 函數的作用是用來更新指針的指向,創建弱引用表。
3..在最后會調用clearDeallocating函數。而clearDeallocating函數首先根據對象的地址獲取weak指針地址的數組,然后緊接着遍歷這個數組,將其中的數組開始置為nil,把這個entry從weak表中刪除,最后一步清理對象的記錄。
問:當weak指向的對象被釋放時,如何讓weak指針置為nil的呢?
1、調用objc_release 2、因為對象的引用計數為0,所以執行dealloc 3、在dealloc中,調用了_objc_rootDealloc函數 4、在_objc_rootDealloc中,調用了object_dispose函數 5、調用objc_destructInstance 6、最后調用objc_clear_deallocating,詳細過程如下: a. 從weak表中獲取廢棄對象的地址為鍵值的記錄 b. 將包含在記錄中的所有附有 weak修飾符變量的地址,賦值為 nil c. 將weak表中該記錄刪除 d. 從引用計數表中刪除廢棄對象的地址為鍵值的記錄
本文講述了weak底層實現原理,也是面試經常被問到的一點,希望對大家有所幫助,謝謝!