上篇文章: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指標的地址(這個地址的值是所指物件指標的地址)陣列。
備註: 更深一層的講解,請參考: