Runtime面試之weak

minjing_lin發表於2019-04-21

上篇文章:Runtime在工作中的運用

1.weak 屬性的特點

weak屬性定義了一種“非擁有關係” 。不論是用作property修飾符還是用來修飾一個變數的宣告,其作用是一樣的,既不增加新物件的引用計數,被釋放時也不會減少新物件的引用計數,同時在屬性所指的物件被銷燬時,屬性值也會置為nil,這樣可以防止野指標錯誤。

2.runtime 如何實現 weak 變數的自動置nil

runtime 對註冊的類會進行佈局,對於 weak 修飾的物件會放入一個 hash 表中。 用 weak 指向的物件記憶體地址作為 key,當此物件的引用計數為0的時候會 dealloc,假如 weak 指向的物件記憶體地址是a,那麼就會以a為鍵, 在這個 weak 表中搜尋,找到所有以a為鍵的 weak 物件,從而設定為 nil。

更細一點的回答:

1.初始化時:runtime會呼叫objc_initWeak函式,初始化一個新的weak指標指向物件的地址。
2.新增引用時:objc_initWeak函式會呼叫objc_storeWeak() 函式, objc_storeWeak() 的作用是更新指標指向,建立對應的弱引用表。
3.釋放時,呼叫clearDeallocating函式。clearDeallocating函式首先根據物件地址獲取所有weak指標地址的陣列,然後遍歷這個陣列把其中的資料設為nil,最後把這個entry從weak表中刪除,最後清理物件的記錄。

3.詳細介紹

參考自《Objective-C高階程式設計》一書

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

{
    NSObject *obj = [[NSObject alloc] init];
    id __weak obj1 = obj;
}
複製程式碼

當我們初始化一個weak變數時,runtime會呼叫 NSObject.mm 中的objc_initWeak函式。

// 編譯器的模擬程式碼
 id obj1;
 objc_initWeak(&obj1, obj);
/*obj引用計數變為0,變數作用域結束*/
 objc_destroyWeak(&obj1);
複製程式碼

通過objc_initWeak函式初始化“附有weak修飾符的變數(obj1)”,在變數作用域結束時通過objc_destoryWeak函式釋放該變數(obj1)。

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

objc_initWeak函式將“附有weak修飾符的變數(obj1)”初始化為0(nil)後,會將“賦值物件”(obj)作為引數,呼叫objc_storeWeak函式。

obj1 = 0;
obj_storeWeak(&obj1, obj);
複製程式碼

也就是說:

weak 修飾的指標預設值是 nil (在Objective-C中向nil傳送訊息是安全的)

然後obj_destroyWeak函式將0(nil)作為引數,呼叫objc_storeWeak函式。

objc_storeWeak(&obj1, 0);
複製程式碼

前面的原始碼與下列原始碼相同。

// 編譯器的模擬程式碼
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
/* ... obj的引用計數變為0,被置nil ... */
objc_storeWeak(&obj1, 0);
複製程式碼

objc_storeWeak函式把第二個引數的賦值物件(obj)的記憶體地址作為鍵值,將第一個引數__weak修飾的屬性變數(obj1)的記憶體地址註冊到 weak 表中。如果第二個引數(obj)為0(nil),那麼把變數(obj1)的地址從weak表中刪除。

由於一個物件可同時賦值給多個附有__weak修飾符的變數中,所以對於一個鍵值,可註冊多個變數的地址。

可以把objc_storeWeak(&a, b)理解為:objc_storeWeak(value, key),並且當key變nil,將value置nil。在b非nil時,a和b指向同一個記憶體地址,在b變nil時,a變nil。此時向a傳送訊息不會崩潰:在Objective-C中向nil傳送訊息是安全的。

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

物件被釋放時呼叫的objc_clear_deallocating函式:

1.從weak表中獲取廢棄物件的地址為鍵值的記錄
2.將包含在記錄中的所有附有 weak修飾符變數的地址,賦值為nil
3.將weak表中該記錄刪除
4.從引用計數表中刪除廢棄物件的地址為鍵值的記錄

總結:

其實Weak表是一個hash(雜湊)表,Key是weak所指物件的地址,Value是weak指標的地址(這個地址的值是所指物件指標的地址)陣列。

備註: 更深一層的講解,請參考:

iOS 底層解析weak的實現原理

細說weak

相關文章