在講__weak修飾符之前,先送上常用的屬性描述.屬性用于封裝數(shù)據(jù),而數(shù)據(jù)則要有"具體的所有權語義".下面的講解的特質僅會影響"設置方法".編譯器在合成存取方法時,會根據(jù)這些修飾符決定所生成的代碼.
- assign: "設置方法"只會執(zhí)行針對"純量類型"(scalar type, 例如 CGFloat或NSInteger 等)的簡單賦值操作;
- strong: 此特質表明該屬性定義了一種"擁有關系"(owning relationship).為這種屬性設置新值時,設置方法會先保留新值,并釋放舊值,然后再將新值設置上去.
- weak: 此特質表明該屬性定義了一種"非擁有關系"(nonowning relationship).為這種屬性設置新值時,設置方法既不保留新值,也不釋放舊值.此特質同assign類似,然而在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out).
- unsafe_unretained: 此特質的語義同assign相同,但是它適用于"對象類型"(object type),該特質表達一種"非擁有關系"("不保留",unretained),當目標對象遭到摧毀時,屬性值不會自動清空("不安全",unsafe),這一點與weak有區(qū)別.
-
copy: 此特質所表達的所屬關系與strong類似.然而設置方法并不保留新值,而是將其"拷貝"(copy).
此處總結下weak修飾符只能用于iOS5以上版本的應用程序,在iOS4用 unsafe_unretained修飾符來代替.盡管ARC式的內存管理是編譯器的工作,但附有unsafe_unretained修飾符的變量不屬于編譯器的內存管理對象.
如果在iOS4的應用程序中必須使用 unsafe_unretained修飾符來代替weak修飾符,要確保被修飾的對象一定存在,否則crash.
如下:
id __unsafe_unretained obj1 = nil;
{
//obj0 變量為強引用,自己生成并持有對象
id __strong obj0 = [[NSObject alloc] init];
//雖然 obj0 變量賦值給了 obj1, 但是 obj1 變量既不持有對象的強引用也不持有對象的弱引用,
//(而帶__weak修飾符的變量持有被修飾對象的弱引用)
obj1 = obj0;
//輸出 obj1 變量表示的對象
NSLog(@"__unsafe_unretained :%@",obj1);
}
//訪問的對象超出作用域,crash
NSLog(@"__unsafe_unretained :%@",obj1);
接下來進入正題,期待已久的__weak修飾符.
- 若附有 __weak修飾符的變量所引用的對象被釋放,則將nil值賦值給該變量;
- 使用附有__weak修飾符的變量,就是使用注冊到autoreleasepool中的對象.
(1)若附有__weak修飾符的變量所引用的對象被釋放,則將nil值賦值給該變量.
下面我們以一個例子來看看內部到底發(fā)生了什么
{
//假設變量 b 附加__strong修飾符且對象被賦值.
id __weak a = b;
}
//編譯器的模擬代碼實現(xiàn)如下:
{
id a;
//(1).通過objc_initWeak()函數(shù)初始化__weak修飾符的變量;
objc_initWeak(&a , b);
//(2).當變量作用域結束時,通過objc_destroyWeak()函數(shù)釋放該變量;
objc_destroyWeak(&a);
}
(1). objc_initWeak(&a , b);
而在上面編譯器模擬代碼中對應轉換如下:
objc_initWeak(&a , b);
轉換為下面代碼
a = 0;
objc_storeWeak(&a , b);
objc_initWeak()函數(shù)將附有__weak修飾符的變量初始化為0后,
將賦值對象b作為參數(shù)調用objc_storeWeak()函數(shù).
objc_storeWeak函數(shù)把第二個參數(shù)(賦值對象b)的內存地址作為鍵值key,將第一個參數(shù)(weak修飾的屬性變量a)的內存地址(&a)作為value,注冊到 weak 表中。如果第二個參數(shù)(b)為0(nil),那么把變量(a)的內存地址(&a)從weak表中刪除。
(也就是初始化一個新的weak指針指向對象的內存地址,objc_storeWeak()函數(shù)的作用是更新指針指向,創(chuàng)建對應的弱引用表.)
把objc_storeWeak(&a, b)理解為:objc_storeWeak(value, key),并且當key變nil,將value置nil。
在b非nil時,a和b指向同一個內存地址,在b變nil時,a變nil。此時向a發(fā)送消息不會崩潰:在Objective-C中向nil發(fā)送消息是安全的。
而如果a是由assign修飾的,則: 在b非nil時,a和b指向同一個內存地址,在b變nil時,a還是指向該內存地址,變野指針。此時向a發(fā)送消息極易崩潰。
(2). objc_destroyWeak(&a);
objc_destroyWeak(&a); => objc_storeWeak(&a , 0);
objc_destroyWeak()函數(shù)將 0 (nil) 作為參數(shù)調用objc_storeWeak()函數(shù).(釋放該變量)
即上面的編譯器模擬代碼等效于下面的代碼
//編譯器的模擬代碼實現(xiàn)如下:
{
id a;
//初始化__weak修飾符的變量
a = 0;
objc_storeWeak(&a , b);
//釋放該變量
objc_storeWeak(&a , 0);
}
在此講解一下objc_storeWeak()這個函數(shù),該函數(shù)把第二個參數(shù)的對象的內存地址作為哈希表的鍵,將第一個參數(shù)即附有 weak修飾符的變量的地址注冊到哈希表中.如果第二個參數(shù)為0,則把變量的地址從哈希表中刪除.
由于一個對象可同時賦值給多個附有weak修飾符的變量中,所以對于一個鍵,可注冊多個變量的地址.
釋放對象程序是怎么實現(xiàn)的呢?如下所示:
(1) objc_release
(2) 因為引用計數(shù)為0所以執(zhí)行dealloc
(3) _objc_rootDealloc
(4) object_dispose
(5) objc_destructInstance
(6) objc_clear_deallocating
對象被釋放時最后調用objc_clear_deallocating()函數(shù)的動作如下:
(1) 從weak表中獲取廢棄對象的地址為鍵值的記錄.
(2) 將包含在記錄中的所有附有__weak修飾符的變量的地址,賦值為nil.
(3) 從weak表中刪除該記錄.
(4) 從引用計數(shù)表中刪除廢棄對象的地址為鍵值的記錄.
如果大量使用附有 weak修飾符的變量,則會消耗相應的CPU資源.良策是只在需要避免循環(huán)引用時使用__weak修飾符.
(2)使用附有__weak修飾符的變量,就是使用注冊到autoreleasepool中的對象.
(因為__weak修飾符只持有對象的弱引用,而在訪問引用對象的過程中,該對象有可能被廢棄。如果把要訪問的對象注冊到autoreleasepool中,那么在@autoreleasepool塊結束之前都能確保該對象的存在。)
以下面的代碼為例:
{
id __weak a = b;
NSLog(@"%@", a);
}
該源代碼可轉換如下:
//編譯器模擬代碼
{
id a;
objc_initWeak(&a , b);
id tmp = objc_loadWeakRetained(&a);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&a);
}
分析:
(1) objc_loadWeakRetained()函數(shù)取出附有__weak修飾符變量所引用的對象并 retain.
(2) objc_autorelease()函數(shù)將對象注冊到autoreleasepool中.
由此可知,因為附有__weak修飾符的變量所引用的對象像這樣被注冊到autoreleasepool中,所以在 @aotoreleasepool 塊結束之前都可以放心使用.但是,如果大量地使用附有__weak修飾符的變量,注冊到autoreleasepool的對象也會大量地增加,因此在使用附有__weak修飾符的變量時,最好先暫時賦值給附有__strong修飾符的變量后再使用.
千里之行,始于足下.