iOS 中 weak 的實現原理

weak底層千千萬,吾竟裝作看不見...

 

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。



鏈接:https://www.jianshu.com/p/3c5e335341e0

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章