1. 程式人生 > >《Objective-C高階程式設計:引用計數和strong ,weak

《Objective-C高階程式設計:引用計數和strong ,weak

轉載請註明出處 如果覺得文章對你有所幫助,請通過留言或關注微信公眾帳號wangzzstrive來支援我,謝謝!

一、前言

這本書由日本人Kazuki Sakamoto和Tomohiko Furumoto所著,主要講了ARC、Blocks、GCD三個模組。總體來說,書的內容講的挺深的,小日本寫的東西還真是不錯。作者一直試圖從原理上闡述ARC、block、GCD的實現機制,不像大部分國內相關書籍中只介紹其然,這是一本探索所以然的書。鑑於上述三個模組是每個試圖在iOS開發上有所造詣的同學必須掌握的東西,強烈建議已經有一定Objective-C功底的同學看看。 從事iOS開發以來一直都是使用手動記憶體管理,因此對引用計數的理解還是比較透徹的。直到開發拉手團購6.0版的時候,開始使用ARC,上手後的感覺是以後的記憶體管理都是ARC的天下。在這本書中,作者也著重介紹了手動記憶體管理及其實現。書中講的內容大部分之前都看過,不過自己看的不夠系統。斷斷續續花了二十天左右時間讀完這本書,還是收穫不小,下面將自認為值得記住的地方列舉出來:

二、引用計數原理

iOS中說到記憶體管理,不管是手動還是ARC,都離不開引用計數,我一直很好奇引用計數到底是怎麼儲存的呢?可能的實現方式有兩種:
  • 鑑於Objective-C物件都有引用計數,所以每個物件都應該儲存自己的引用計數
  • runtime統一儲存所有物件的引用計數
GNUstep使用的是前一種思想,在這裡就不詳述了。 蘋果的實現方式是後一種----runtime採用散列表來管理引用計數,儲存形式如下圖所示:
蘋果在實現時,凡是使用引用計數的方法,包括retainCount、retain、release方法都呼叫了同一個方法,該方法的實現原理簡化後如下所示:
  1. int _CFDoExternRefOperation(
    uintptr_t op, id obj)  
  2. {  
  3.   CFBasicHashRef table = 取得物件的散列表(obj);  
  4.   int count;  
  5.   switch(op) {  
  6.   case OPERATION_retainCount;  
  7.     count = CFBasicHashGetCountOfKey(table, obj);  
  8.     return count;  
  9.   case OPERATION_retain:  
  10.     CFBasicHashAddValue(table, obj);  
  11.     return obj;  
  12.   case OPERATION_release
    :  
  13.     count = CFBasicHashRemoveValue(table, obj);  
  14.     return0 == count;  
  15.   }  
  16. }  
這一個方法就解釋了引用計數的實現原理!
蘋果這種實現方式的優點是: 不用再每個物件記憶體塊中考慮引用計數所站的記憶體; 引用計數表中儲存的有各個物件的記憶體地址,可以直接通過各條引用技術追蹤到對應的物件記憶體塊,在除錯的時候很有用。

三、__autoreleasing關鍵字

看我完這本書後,對該修飾符有了更深的理解,有以下幾個方面: 1、ARC下使用__autoreleasing修飾符修飾的變數會被註冊到autoreleasepool中,和非ARC下呼叫autorelease方法的效果相同; 2、當方法名不是alloc/new/copy/mutableCopy時,使用return返回的物件強引用會被自動註冊到autoreleasepool。比如:
  1. + (id)array  
  2. {  
  3.   id obj = [[NSArray alloc] init];  
  4.   return obj;  
  5. }  
上述程式碼中,因為沒有指定所有權修飾符,id obj就是預設的id __strong obj,即obj是強引用,這時,obj就會被新增到autoreleasepool中。 3、訪問附有__weak修飾符的變數時,該變數會被自動註冊到autoreleasepool中,比如:
  1. id __weak obj1 = obj0;  
  2. NSLog("class=%@", [obj1 class]);  
  3. //等價於以下程式碼
  4. id __weak obj1 = obj0;  
  5. id __autoreleasing tmp = obj1;  
  6. NSLog("class=%@", [tmp class]);  
這種等價是編譯器自動做的轉換,原因是__weak修飾符支援有物件的弱引用,在訪問引用物件的過程中,該物件可能被釋放。而如果將該物件加入到autoreleasepool中,在pool被釋放之前,tmp對該物件的引用都是有效的。 4、_objc_autoreleasePoolPrint()函式 該函式屬於私有函式,可以打印出註冊到呼叫函式處所屬的autoreleasepool中的物件。

四、__weak關鍵字

這部分需要著重介紹的是__weak關鍵字。 我們知道,當一個__weak型別的指標指向的物件被釋放時,該指標會自動被置成nil,因此__weak關鍵字修飾的指標又被稱為智慧指標。那麼這個功能是如何實現的呢? 1、編譯器做的事 先看下面這段程式碼:
  1. {  
  2.   id __weak obj1 = obj;  
  3. }  
會被編譯器編譯成下面的模擬程式碼:
  1. id obj1;  
  2. objc_initWeak(&obj1, obj);  
  3. objc_destoryWeak(&obj1);  
即編譯器會通過objc_initWeak函式初始化__weak修飾的變數,當變數的作用域結束後會通過objc_destoryWeak函式釋放該變數。objc_initWeak函式實際乾的活是:
  1. objc1 = 0;  
  2. objc_storeWeak(&obj1, obj);  
這裡是先將指標objc1置成0,再呼叫objc_storeWeak函式使得obj1指向obj物件。 接下來的objc_destoryWeak函式的實際操作如下:
  1. objc_storeWeak(&obj10);  
也就是說,讓obj1指標指向的內容變成空。 總結一下,剛才的整個過程模擬程式碼如下:
  1. id obj1;  
  2. obj1 = 0;  
  3. objc_storeWeak(&obj1, obj);  
  4. objc_storeWeak(&obj10);  
2、__weak實現原理 實際上,objc_storeWeak函式會把第二個引數的物件的地址作為key,並將第一個引數(__weak關鍵字修飾的指標的地址)作為值,註冊到weak表中。如果第二個引數為0(說明對應的物件被釋放了),則將weak表中將整個key-value鍵值對刪除,這就是__weak關鍵字的核心思想! weak表和引用計數表類似,都是通過hash表實現的。如果使用weak表,將被釋放的物件地址作為key去檢索,就能很高效的獲取對應的指向該物件的型別為__weak的指標變數的地址。同時很容易理解,一個物件可能有多個__weak指標指向,因此一個物件地址key可能對應多個值。 在呼叫物件的release方法時,會在其中一步呼叫objc_clear_deallocating函式,該函式會執行以下操作:
  • 以當前物件的地址作為key,從weak表中獲取對應的值----指向該物件的__weak型別的指標變數;
  • 將取到的所有指標變數的值賦值為nil;
  • 從weak表中刪除該key對應的整條記錄。
根據以上步驟,前面介紹過的__weak關鍵字修飾的物件指標所指向的物件被釋放時,指標被置為nil就可以實現了。同時由此可知,如果大量使用附有__weak修飾符的變數會消耗響應的CPU資源,因此,應該儘量少使用__weak修飾符。