1. 程式人生 > >iOS 底層解析weak的實現原理

iOS 底層解析weak的實現原理

參考地址---------:http://www.cocoachina.com/ios/20170328/18962.html

weak 實現原理的概括

Runtime維護了一個weak表,用於儲存指向某個物件的所有weak指標。weak表其實是一個hash(雜湊)表,Key是所指物件的地址,Value是weak指標的地址陣列,為什麼是指標的地址而不是直接是指標的陣列?我認為只得到指標沒法做後續更改指標的指向,比如置nil,或是內部演算法,整改指向都受限。

weak 的實現原理可以概括一下三步:

1、初始化時:runtime會呼叫objc_initWeak函式,初始化一個新的weak指標指向物件的地址。

2、新增引用時:objc_initWeak函式會呼叫 objc_storeWeak() 函式, objc_storeWeak() 的作用是更新指標指向,建立對應的弱引用表。

3、釋放時,呼叫clearDeallocating函式。clearDeallocating函式首先根據物件地址獲取所有weak指標地址的陣列,然後遍歷這個陣列把其中的資料設為nil,最後把這個entry從weak表中刪除,最後清理物件的記錄。

下面將開始詳細介紹每一步:

1、初始化時:runtime會呼叫objc_initWeak函式,objc_initWeak函式會初始化一個新的weak指標指向物件的地址。

示例程式碼:

1

2

3

4

{

    NSObject *obj = [[NSObject alloc] init];

    id __weak obj1 = obj;

}

當我們初始化一個weak變數時,runtime會呼叫 NSObject.mm 中的objc_initWeak函式。這個函式在Clang中的宣告如下:

1

id objc_initWeak(id *object, id value);

而對於 objc_initWeak() 方法的實現

1

2

3

4

5

6

7

8

9

10

11

12

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_msgSend 中的快取判斷),這裡判斷了其指標指向的類物件是否有效,無效直接釋放,不再往深層呼叫函式。否則,object將被註冊為一個指向value的__weak物件。而這事應該是objc_storeWeak函式乾的。

objc_initWeak、objc_storeWeak  大致做了如下圖相關操作,更新維護兩張表,還需要加鎖保證多執行緒一致性,更新新舊雜湊表。

QQ截圖20170327155908.png

 

 

2、釋放時,呼叫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

重點看物件被釋放時呼叫的objc_clear_deallocating函式。該函式實現如下:

1

2

3

4

5

6

7

void  objc_clear_deallocating(id obj) 

{

    assert(obj);

    assert(!UseGC);

    if (obj->isTaggedPointer()) return;

   obj->clearDeallocating();

}

也就是呼叫了clearDeallocating,繼續追蹤可以發現,它最終是使用了迭代器來取weak表的value,然後呼叫weak_clear_no_lock,然後查詢對應的value,將該weak指標置空,weak_clear_no_lock函式的實現如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

/**

 * Called by dealloc; nils out all weak pointers that point to the

 * provided object so that they can no longer be used.

 *

 * @param weak_table

 * @param referent The object being deallocated.

 */

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);

    if (entry == nil) {

        /// XXX shouldn't happen, but does with mismatched CF/objc

        //printf("XXX no entry for clear deallocating %p\n", referent);

        return;

    }

    // zero out references

    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;

            }

            else if (*referrer) {

                _objc_inform("__weak variable at %p holds %p instead of %p. "

                             "This is probably incorrect use of "

                             "objc_storeWeak() and objc_loadWeak(). "

                             "Break on objc_weak_error to debug.\n",

                             referrer, (void*)*referrer, (void*)referent);

                objc_weak_error();

            }

        }

    }

    weak_entry_remove(weak_table, entry);

}

objc_clear_deallocating該函式的動作如下:

1、從weak表中獲取廢棄物件的地址為鍵值的記錄

2、將包含在記錄中的所有附有 weak修飾符變數的地址,賦值為nil

3、將weak表中該記錄刪除

4、從引用計數表中刪除廢棄物件的地址為鍵值的記錄

看了objc-weak.mm的原始碼就明白了:其實Weak表是一個hash(雜湊)表,然後裡面的key是指向物件的地址,Value是Weak指標的地址的陣列。