__weak修飾符的實現(xiàn)原理

在講__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修飾符的變量后再使用.

千里之行,始于足下.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。