weak基本用法
weak是弱引用,用weak描述修飾或者所引用對象的計數器不會加一,并且會在引用的對象被釋放的時候自動被設置為nil,大大避免了野指針訪問壞內存引起崩潰的情況,另外weak還可以用于解決循環引用。
weak原理概括
weak表其實是一個hash(哈希)表,Key是所指對象的地址,Value是weak指針的地址數組。weak的底層實現的原理是什么?
Runtime維護了一個weak表,用于存儲指向某個對象的所有weak指針。weak表其實是一個hash表,Key是所指對象的地址,value是weak指針的地址(這個地址的值是所指對象指針的地址)數組。
為什么value是數組?因為一個對象可能被多個弱引用指針指向
weak原理實現步驟
weak 的實現原理可概括三步:
1、初始化時:runtime會調用objc_initWeak函數,初始化一個新的weak指針指向對象的地址。
初始化流程圖
2、添加引用時:objc_initWeak函數會調用 objc_storeWeak() 函數, objc_storeWeak() 的作用是更新指針指向,創建對應的弱引用表。
3、釋放時,調用clearDeallocating函數。clearDeallocating函數首先根據對象地址獲取所有weak指針地址的數組,然后遍歷這個數組把其中的數據設為nil,最后把這個entry從weak表中刪除,最后清理對象的記錄。
weak實現三步驟詳細過程:
1、初始化時:runtime會調用objc_initWeak函數,objc_initWeak函數會初始化一個新的weak指針指向對象的地址。
示例代碼:
NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;
當我們初始化一個weak變量時,runtime會調用 NSObject.mm 中的objc_initWeak函數。
這個函數在Clang中的聲明如下:
id objc_initWeak(id *object, id value);
而對于 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);
}
這里先判斷了其指針指向的類對象是否有效,無效直接釋放返回,不再往深層調用函數。否則,object將通過bjc_storeWeak函數被注冊為一個指向value的__weak對象。
注意:objc_initWeak函數有一個前提條件:就是object必須是一個沒有被注冊為__weak對象的有效指針。而value則可以是null,或者指向一個有效的對象。
2、添加引用時:objc_initWeak函數會調用 objc_storeWeak() 函數, objc_storeWeak() 的作用是更新指針指向,創建對應的弱引用表。
objc_storeWeak的函數聲明如下:
id objc_storeWeak(id *location, id value);
objc_storeWeak() 的具體實現,請參考weak弱引用實現的方式,這里的實現很復雜,沒看懂,沒看懂。
3、釋放時,調用clearDeallocating函數。clearDeallocating函數首先根據對象地址獲取所有weak指針地址的數組,然后遍歷這個數組把其中的數據設為nil,最后把這個entry從weak表中刪除,最后清理對象的記錄。
當weak引用指向的對象被釋放時,又是如何去處理weak指針的呢?當釋放對象時,其基本流程如下:
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,__unsafe_unretained, unowned 與 assign區別
__unsafe_unretained: 不會對對象進行retain,當對象銷毀時,會依然指向之前的內存空間(野指針)
weak: 不會對對象進行retain,當對象銷毀時,會自動指向nil
assign: 實質與__unsafe_unretained等同
unsafe_unretained也可以修飾代表簡單數據類型的property,weak也不能修飾用來代表簡單數據類型的property。
__unsafe_unretained 與 weak 比較,使用 weak 是有代價的,因為通過上面的原理可知,__weak需要檢查對象是否已經消亡,而為了知道是否已經消亡,自然也需要一些信息去跟蹤對象的使用情況。也正因此,__unsafe_unretained 比 __weak快,所以當明確知道對象的生命期時,選擇__unsafe_unretained 會有一些性能提升,這種性能提升是很微小的。但當很清楚的情況下,__unsafe_unretained 也是安全的,自然能快一點是一點。而當情況不確定的時候,應該優先選用 __weak 。
unowned使用在Swift中,也會分 weak 和 unowned。unowned 的含義跟 __unsafe_unretained 差不多。假如很明確的知道對象的生命期,也可以選擇 unowned。
致謝
參考博客:
weak 弱引用的實現方式
iOS 底層解析weak的實現原理(包含weak對象的初始化,引用,釋放的分析)