內存管理(四) - 源碼解讀所有權修飾符(一)

CSDN

有道雲

__strong 修飾符

1. alloc/new/copy/mutableCopy

賦值給附有__strong 修飾符的變量在實際的程序中到底是這麼樣運行的呢?

{
    id __strong obj = [[NSObject alloc] init];
}

該源碼實際上可以轉換爲調用以下函數

id obj = objc_msgsend(NSObject, @selector(alloc));
objc_msgSend(obj, @selectro(init));
objc_release(obj);

2次調用了objc_msgSend方法, 變量作用域結束時通過 objc_release 釋放對象。雖然 ARC 時不能使用 release 方法,但由此可知編譯器自動插入了 release

2. alloc/new/copy/mutableCopy 之外

{
     id __strong obj = [NSMutableArray array];
}

雖然調用了我們熟知的 NSMutableArray 類的 array 類方法,但得到的結果卻與之前稍有不同。

id obj = objc_msgsend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
objc_retainAutoreleasedReturnValue 函數

objc_retainAutoreleasedReturnValue 函數主要用於最優化程序運行。

顧名思義,它是用於自己持有對象的函數,但它持有的對象應該返回註冊在 autoreleasePool 中對象的方法,或者是函數的返回值。像這種,非alloc/new/copy/mutableCopy 的方法,比如 NSMutableArray 類的 array 類方法等調用之後,由編譯器插入該函數。

這種 objc_retainAutoreleasedReturnValue 函數是成對的,與之相對的函數是 objc_autoreleaseReturnValue

objc_autoreleaseReturnValue 函數

objc_autoreleaseReturnValue 用於 非alloc/new/copy/mutableCopy 返回對象的實現上。

+ (id) array {
    retrun [[NSMutableArray alloc] init];
}

源碼如下, 調用看 objc_autoreleaseReturnValue 函數

+ (id) array {
   id obj = objc_msgsend(NSMutableArray, @selector(alloc));
   objc_msgSend(obj, @selectro(init));
   retrun objc_autoreleaseReturnValue(obj);
}

像該源代碼這樣,通過使用 objc_autoreleaseReturnValue 函數,返回註冊到 autoreleasePool 中的對象。

但是 objc_autoreleaseReturnValue 函數 與 objc_autorelease 函數不同,一般不僅限於註冊到 autoreleasePool 中。

objc_autoreleaseReturnValue 函數會檢查使用該函數的方法或者函數調用方的執行命令列表,如果!!! 在方法或者函數的調用方在調用了方法或者函數後緊接着調用了 objc_retainAutoreleasedReturnValue函數,那麼就不將返回的對象註冊到 autoreleasePool 中,而是直接傳遞到方法或者函數的調用方。

小結

objc_retainAutoreleasedReturnValue 函數與 objc_retain 函數不同,它即便不註冊到 autoreleasePool 中而返回對象,也能夠正確的獲取對象。

通過 objc_retainAutoreleasedReturnValueobjc_autoreleaseReturnValue 函數的協作,可以將對象不註冊到 autoreleasePool 中而直接傳遞, 這一過程到達了最優化。

__weak 修飾符

就像前面我們看到的一樣, __wea 修飾符提供的功能如同魔法一般。

  • 若附有__weak 修飾符的變量所引用的對象被廢棄,則將 nil 賦值給該變量。
  • 使用附有__weak 修飾符的變量,即是使用註冊到 autoreleasePool 中的對象。

這些功能就像魔法一樣,到底發生了什麼,我們一無所知。

{
    id __weak obj1 = obj;
}

假設變量 obj 附有 __strong 修飾符且對象被賦值.

<!--編譯器的模擬代碼-->
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1); 

通過 objc_initWeak 函數初始化附有 __weak 修飾符的變量。

在變量作用域結束時通過 objc_destroyWeak 函數釋放該變量。

如下面代碼所示:

objc_initWeak 函數將附有 __weak 修飾符的 變量obj1 初始化爲 0 後,會將賦值的對象obj作爲參數調用 objc_storeWeak 函數。

obj1 = 0;

objc_storeWeak(&obj1, obj);

objc_destroyWeak 函數將0做參數調用 objc_storeWeak 函數

objc_storeWeak((&obj1, obj)

即使前面的源碼和下面源代碼相同

id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0); 
<!--賦值初始化傳值,銷燬傳0(看到這裏我的理解)-->

objc_storeWeak 函數

objc_storeWeak 函數把第二參數的賦值對象的地址作爲鍵值,將第一個參數附有__weak 修飾符的變量的地址註冊到 weak 表中。如果第二參數爲0,則把變量的地址從 weak 表中刪除。(驗證我前面的理解)

weak 表與引用計數表相同,作爲散列表被實現。如果使用 weak表,將廢棄對象的地址作爲鍵值進行檢索,就能高效地獲取對應的附有 __weak 修飾符的變量的地址。另外,由於一個對象可同時賦值給多個附有 __weak 修飾符的變量中,所以對於一個鍵值,可註冊多個變量的地址。

釋放對象

釋放對象時,廢棄誰都不持有的對象的同時,程序的動作是怎樣的呢?下面我們來跟蹤觀察。對象將通過 objc_release 函數釋放。

(1) objc_release

(2) 因爲引用計數爲0所以執行 dealloc

(3) _objc_rootDealloc

(4) objc_dispose

(5) objc_destructInstance   //銷燬實例

(6) objc_clear_deallocating //清除回收

對象被廢棄時最後調用的 objc_clear_deallocating 函數的動作如下:

(1) 從 weak 表中獲取廢棄對象的地址爲鍵值的紀錄。

(2) 將包含在記錄中的所有附有 __weak 修飾符變量的地址,賦值爲nil。

(3) 從 weak 表中刪除該紀錄。

(4) 從引用計數表中刪除廢棄對象的地址爲鍵值的紀錄。

根據以上步驟,前面說的如果附有 __weak 修飾符的變量所引用的對象被廢棄,則將 nil 值賦給該變量這一功能即被實現。由此可知如果有大量使用附有 __weak 修飾符的變量,則會消耗相應的 CPU 資源。良策是隻在需要避免循環引用時使用 __weak 修飾符。

使用 __weak 修飾符的修飾自己生成並持有的對象

{
  id __weak obj = [[NSObject alloc] init];
}

將自己生成並持有的對象賦值給附有 __weak 修飾符的變量,所以自己並不能持有該對象,這時會被釋放並被廢棄,因此會引起編譯器警告。

id obj ;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_initWeak(&obj, tmp);
obj_release(tmp);
objc_destroyWeak(&obj);

雖然自己生成並持有的對象通過 objc_initWeak 函數被賦值給附有 __weak 修飾符的變量中,但編譯器判斷其並沒有持有者,故該對象立即通過 objc_release 函數被釋放和廢棄。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章