1. 程式人生 > >iOS/OS X記憶體管理(一):基本概念與原理

iOS/OS X記憶體管理(一):基本概念與原理

CSDN移動將持續為您優選移動開發的精華內容,共同探討移動開發的技術熱點話題,涵蓋移動應用、開發工具、移動遊戲及引擎、智慧硬體、物聯網等方方面面。如果您想投稿、尋求《近匠》報道,或給文章挑錯,歡迎傳送郵件至tangxy#csdn.net(請把#改成@)。 

在Objective-C的記憶體管理中,其實就是引用計數(reference count)的管理。記憶體管理就是在程式需要時程式設計師分配一段記憶體空間,而當使用完之後將它釋放。如果程式設計師對記憶體資源使用不當,有時不僅會造成記憶體資源浪費,甚至會導致程式crach。我們將會從引用計數和記憶體管理規則等基本概念開始,然後講述有哪些記憶體管理方法,最後注意有哪些常見記憶體問題。 

 

memory management from apple document 

基本概念

引用計數(Reference Count)

為了解釋引用計數,我們做一個類比:員工在辦公室使用燈的情景。

 

引用Pro Multithreading and Memory Management for iOS and OS X的圖

  • 當第一個人進入辦公室時,他需要使用燈,於是開燈,引用計數為1;
  • 當另一個人進入辦公室時,他也需要燈,引用計數為2;每當多一個人進入辦公室時,引用計數加1;
  • 當有一個人離開辦公室時,引用計數減1,當引用計數為0時,也就是最後一個人離開辦公室時,他不再需要使用燈,關燈離開辦公室。

記憶體管理規則

從上面員工在辦公室使用燈的例子,我們對比一下燈的動作與Objective-C物件的動作有什麼相似之處:

 

因為我們是通過引用計數來管理燈,那麼我們也可以通過引用計數來管理使用Objective-C物件。

 

引用Pro Multithreading and Memory Management for iOS and OS X的圖

而Objective-C物件的動作對應有哪些方法以及這些方法對引用計數有什麼影響?

 

當你alloc一個物件objc,此時RC=1;在某個地方你又retain這個物件objc,此時RC加1,也就是RC=2;由於呼叫alloc/retain一次,對應需要呼叫release一次來釋放物件objc,所以你需要release物件objc兩次,此時RC=0;而當RC=0時,系統會自動呼叫dealloc方法釋放物件。

Autorelease Pool

在開發中,我們常常都會使用到區域性變數,區域性變數一個特點就是當它超過作用域時,就會自動釋放。而autorelease pool跟區域性變數類似,當執行程式碼超過autorelease pool塊時,所有放在autorelease pool的物件都會自動呼叫release。它的工作原理如下:

  • 建立一個NSAutoreleasePool物件;
  • 在autorelease pool塊的物件呼叫autorelease方法;
  • 釋放NSAutoreleasePool物件。

 

引用Pro Multithreading and Memory Management for iOS and OS X的圖

iOS 5/OS X Lion前的(等下會介紹引入ARC的寫法)例項程式碼如下:

  1. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];  
  2. // put object into pool
  3. id obj = [[NSObject alloc] init];  
  4. [obj autorelease];  
  5. [pool drain];  
  6. /* 超過autorelease pool作用域範圍時,obj會自動呼叫release方法 */

由於放在autorelease pool的物件並不會馬上釋放,如果有大量圖片資料放在這裡的話,將會導致記憶體不足。 

  1. for (int i = 0; i < numberOfImages; i++)  
  2. {  
  3.       /*   處理圖片,例如載入 
  4.        *   太多autoreleased objects存在 
  5.        *   由於NSAutoreleasePool物件沒有被釋放 
  6.        *   在某個時刻,會導致記憶體不足  
  7.        */
  8. }  

ARC管理方法

iOS/OS X記憶體管理方法有兩種:手動引用計數(Manual Reference Counting)和自動引用計數(Automatic Reference Counting)。從OS X Lion和iOS 5開始,不再需要程式設計師手動呼叫retain和release方法來管理Objective-C物件的記憶體,而是引入一種新的記憶體管理機制Automatic Reference Counting(ARC),簡單來說,它讓編譯器來代替程式設計師來自動加入retain和release方法來持有和放棄物件的所有權。

在ARC記憶體管理機制中,id和其他物件型別變數必須是以下四個ownership qualifiers其中一個來修飾:

  • __strong(預設,如果不指定其他,編譯器就預設加入)
  • __weak
  • __unsafe_unretained
  • __autoreleasing

所以在管理Objective-C物件記憶體的時候,你必須選擇其中一個,下面會用一些列子來逐個解釋它們的含義以及如何選擇它們。

__strong ownership qualifier

如果我想建立一個字串,使用完之後將它釋放呼叫,使用MRC管理記憶體的寫法應該是這樣:

  1. {  
  2.     NSString *text = @"Hello, world";    //@"Hello, world"物件的RC=1
  3.     NSLog(@"%@", text);  
  4.     [text release];                      //@"Hello, world"物件的RC=0
  5. }  

而如果是使用ARC方式的話,就text物件無需呼叫release方法,而是當text變數超過作用域時,編譯器來自動加入[text release]方法來釋放記憶體。 

  1. {  
  2.     NSString *text = @"Hello, world";    //@"Hello, world"物件的RC=1
  3.     NSLog(@"%@", text);  
  4. }  
  5. /* 
  6.  *  當text超過作用域時,@"Hello, world"物件會自動釋放,RC=0 
  7.  */

而當你將text賦值給其他變數anotherText時,MRC需要retain一下來持有所有權,當text和anotherText使用完之後,各個呼叫release方法來釋放。 

  1. {  
  2.     NSString *text = @"Hello, world";    //@"Hello, world"物件的RC=1
  3.     NSLog(@"%@", text);  
  4.     NSString *anotherText = text;        //@"Hello, world"物件的RC=1
  5.     [anotherText retain];                //@"Hello, world"物件的RC=2
  6.     NSLog(@"%@", anotherText);  
  7.     [text release];                      //@"Hello, world"物件的RC=1
  8.     [anotherText release];               //@"Hello, world"物件的RC=0
  9. }  

而使用ARC的話,並不需要呼叫retain和release方法來持有跟釋放物件。 

  1. {  
  2.     NSString *text = @"Hello, world";    //@"Hello, world"物件的RC=1
  3.     NSLog(@"%@", text);  
  4.     NSString *anotherText = text;        //@"Hello, world"物件的RC=2
  5.     NSLog(@"%@", anotherText);  
  6. }  
  7. /* 
  8.  *  當text和anotherText超過作用域時,會自動呼叫[text release]和[anotherText release]方法, @"Hello, world"物件的RC=0 
  9.  */

除了當__strong變數超過作用域時,編譯器會自動加入release語句來釋放記憶體,如果你將__strong變數重新賦給它其他值,那麼編譯器也會自動加入release語句來釋放變數指向之前的物件。例如: 

  1. {  
  2.     NSString *text = @"Hello, world";    //@"Hello, world"物件的RC=1
  3.     NSString *anotherText = text;        //@"Hello, world"物件的RC=2
  4.     NSString *anotherText = @"Sam Lau";  // 由於anotherText物件引用另一個物件@"Sam Lau",那麼就會自動呼叫[anotherText release]方法,使得@"Hello, world"物件的RC=1, @"Sam Lau"物件的RC=1
  5. }  
  6. /* 
  7.  *  當text和anotherText超過作用域時,會自動呼叫[text release]和[anotherText release]方法, 
  8.  *  @"Hello, world"物件的RC=0和@"Sam Lau"物件的RC=0 
  9.  */
如果變數var被__strong修飾,當變數var指向某個物件objc,那麼變數var持有某個物件objc的所有權。 

前面已經提過記憶體管理的四條規則: 

 

我們總結一下編譯器是按以下方法來實現的:

  • 對於規則1和規則2,是通過__strong變數來實現;
  • 對於規則3來說,當變數超過它的作用域或被賦值或成員變數被丟棄時就能實現;
  • 對於規則4,當RC=0時,系統就會自動呼叫。

__weak ownership qualifier

其實編譯器根據__strong修飾符來管理物件記憶體。但是__strong並不能解決引用迴圈(Reference Cycle)問題:物件A持有物件B,反過來,物件B持有物件A;這樣會導致不能釋放記憶體造成記憶體洩露問題。

引用Pro Multithreading and Memory Management for iOS and OS X的圖

舉一個簡單的例子,有一個類Test有個屬性objc,有兩個物件test1和test2的屬性objc互相引用test1和test2:

  1. @interface Test : NSObject  
  2. @property (strong, nonatomic) id objc;  
  3. @end  
  1. {  
  2.     Test *test1 = [Test new];        /* 物件a */
  3.     /* test1有一個強引用到物件a */
  4.     Test *test2 = [Test new];        /* 物件b */
  5.     /* test2有一個強引用到物件b */
  6.     test1.objc = test2;              /* 物件a的成員變數objc有一個強引用到物件b */
  7.     test2.objc = test1;              /* 物件b的成員變數objc有一個強引用到物件a */
  8. }  
  9. /*   當變數test1超過它作用域時,它指向a物件會自動release 
  10.  *   當變數test2超過它作用域時,它指向b物件會自動release 
  11.  *    
  12.  *   此時,b物件的objc成員變數仍持有一個強引用到物件a 
  13.  *   此時,a物件的objc成員變數仍持有一個強引用到物件b 
  14.  *   於是發生記憶體洩露 
  15.  */

如何解決?於是我們引用一個__weakownership qualifier,被它修飾的變數都不持有物件的所有權,而且當變數指向的物件的RC為0時,變數設定為nil。例如: 

  1. __weak NSString *text = @"Sam Lau";  
  2. NSLog(@"%@", text);  

由於text變數被__weak修飾,text並不持有@"Sam Lau"物件的所有權,@"Sam Lau"物件一建立就馬上被釋放,並且編譯器給出警告⚠️,所以列印結果為(null)。

所以,針對剛才的引用迴圈問題,只需要將Test類的屬性objc設定weak修飾符,那麼就能解決。

  1. @interface Test : NSObject  
  2. @property (weak, nonatomic) id objc;  
  3. @end  
  1. {  
  2.     Test *test1 = [Test new];        /* 物件a */
  3.     /* test1有一個強引用到物件a */
  4.     Test *test2 = [Test new];        /* 物件b */
  5.     /* test2有一個強引用到物件b */
  6.     test1.objc = test2;              /* 物件a的成員變數objc不持有物件b */
  7.     test2.objc = test1;              /* 物件b的成員變數objc不持有物件a */
  8. }  
  9. /*   當變數test1超過它作用域時,它指向a物件會自動release 
  10.  *   當變數test2超過它作用域時,它指向b物件會自動release 
  11.  */

__unsafe_unretained ownership qualifier

__unsafe_unretained ownership qualifier,正如名字所示,它是不安全的。它跟__weak相似,被它修飾的變數都不持有物件的所有權,但當變數指向的物件的RC為0時,變數並不設定為nil,而是繼續儲存物件的地址;這樣的話,物件有可能已經釋放,但繼續訪問,就會造成非法訪問(Invalid Access)。例子如下:

  1. __unsafe_unretained id obj0 = nil;  
  2. {  
  3.     id obj1 = [[NSObject alloc] init];     // 物件A
  4.     /* 由於obj1是強引用,所以obj1持有物件A的所有權,物件A的RC=1 */
  5.     obj0 = obj1;  
  6.     /* 由於obj0是__unsafe_unretained,它不持有物件A的所有權,但能夠引用它,物件A的RC=1 */
  7.     NSLog(@"A: %@", obj0);  
  8. }  
  9. /* 當obj1超過它的作用域時,它指向的物件A將會自動釋放 */
  10. NSLog(@"B: %@", obj0);  
  11. /* 由於obj0是__unsafe_unretained,當它指向的物件RC=0時,它會繼續儲存物件的地址,所以兩個地址相同 */

列印結果是記憶體地址相同: 

 

如果將__unsafe_unretained改為weak的話,兩個列印結果將不同。 

  1. __weak id obj0 = nil;  
  2. {  
  3.     id obj1 = [[NSObject alloc] init];     // 物件A
  4.     /* 由於obj1是強引用,所以obj1持有物件A的所有權,物件A的RC=1 */
  5.     obj0 = obj1;  
  6.     /* 由於obj0是__unsafe_unretained,它不持有物件A的所有權,但能夠引用它,物件A的RC=1 */
  7.     NSLog(@"A: %@", obj0);  
  8. }  
  9. /* 當obj1超過它的作用域時,它指向的物件A將會自動釋放 */
  10. NSLog(@"B: %@", obj0);  
  11. /* 由於obj0是__weak, 當它指向的物件RC=0時,它會自動設定為nil,所以兩個列印結果將不同*/

 

__autoreleasing ownership qualifier

引入ARC之後,讓我們看看autorelease pool有哪些變化。沒有ARC之前的寫法如下:

  1. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];  
  2. // put object into pool
  3. id obj = [[NSObject alloc] init];  
  4. [obj autorelease];  
  5. [pool drain];  
  6. /* 超過autorelease pool作用域範圍時,obj會自動呼叫release方法 */

引入ARC之後,寫法比之前更加簡潔: 

  1. @autoreleasepool {  
  2.     id __autoreleasing obj = [[NSObject alloc] init];  
  3. }  

相比之前的建立、使用和釋放NSAutoreleasePool物件,現在你只需要將程式碼放在@autoreleasepool塊即可。你也不需要呼叫autorelease方法了,只需要用__autoreleasing修飾變數即可。 

 

引用Pro Multithreading and Memory Management for iOS and OS X的圖 

但是我們很少或基本上不使用autorelease pool。當我們使用XCode建立工程後,有一個app的入口檔案main.m使用了它: 

  1. int main(int argc, char * argv[]) {  
  2.     @autoreleasepool {  
  3.         

    相關推薦

    iOS/OS X記憶體管理基本概念原理

    CSDN移動將持續為您優選移動開發的精華內容,共同探討移動開發的技術熱點話題,涵蓋移動應用、開發工具、移動遊戲及引擎、智慧硬體、物聯網等方方面面。如果您想投稿、尋求《近匠》報道,或給文章挑錯,歡迎傳送郵件至tangxy#csdn.net(請把#改成@)。  在Objective-C的記憶體管理中,其實就

    作業系統核心原理-5.記憶體管理基本記憶體管理

      作業系統的兩個角色分別是魔術師和管理者,在管理者這個角色中,除了CPU之外,記憶體是作業系統要管理的另外一個重要資源。記憶體管理需要達到兩個目標:一是地址保護,即一個程式不能訪問另一個程式的地址空間。二是地址獨立,即程式發出的地址應該與物理主存地址無關。這兩個目標就是衡量一個記憶體管理系統是否完善的標準,

    Python爬蟲基本概念

    popu 通用 字符 spider dai 自身 部分 螞蟻 people 網絡爬蟲的定義 網絡爬蟲(Web Spider。又被稱為網頁蜘蛛。網絡機器人,又稱為網頁追逐者),是一種依照一定的規則,自己主動的抓取萬維網信息的程序或者腳本。另外一些不常使用

    各種音視訊編解碼學習詳解之 編解碼學習筆記基本概念

    最近在研究音視訊編解碼這一塊兒,看到@bitbit大神寫的【各種音視訊編解碼學習詳解】這篇文章,非常感謝,佩服的五體投地。奈何大神這邊文章太長,在這裡我把它分解很多小的篇幅,方便閱讀。大神部落格傳送門:https://www.cnblogs.com/skyofbitbit/p/3651270.htm

    JVM調優總結基本概念

    一、資料型別   Java虛擬機器中,資料型別可以分為兩類:基本型別和引用型別。   基本型別的變數儲存原始值,即:他代表的值就是數值本身; 而引用型別的變數儲存引用值。“引用值”代表了某個物件的引用,而不是物件本身,物件本身存放在這個引用值所表示的地址的位置。

    Android二維碼掃描開發實現思路原理

    【 回覆“ 1024 ”,送你一個特別推送 】 現在二維碼已經非常普及了,那麼二維碼的掃描與處理也成為了Android開發中的一個必要技能。網上有很多關於Android中二維碼處理的帖子,大都是在講開源框架zxing用法,然後貼貼程式碼就完了,並沒有一個系統的分析和

    Netty 入門基本元件執行緒模型

      Netty 的學習內容主要是圍繞 TCP 和 Java NIO 這兩個點展開的,後文中所有的內容如果沒有特殊說明,那麼所指的內容都是與這兩點相關的。由於 Netty 是基於 Java NIO 的 API 之上構建的網路通訊框架,Java NIO 中的幾個元件,都能在 Netty 中找到對應的封裝。下面我們

    VxWorks6.6 pcPentium BSP 使用說明基本概念

    “VxWorks6.6 BSP 使用說明”將釋出pcPentium和idp945兩個系列的BSP的使用說明。每個系列約5篇文章。之後還將釋出由這兩個官方提供的BSP的實戰移植方法。 本說明適用範圍 p

    GCD教程基本概念

    什麼是GCD? Grand Central Dispatch或者GCD,是一套低層API,提供了一種新的方法來進行併發程式編寫。從基本功能上講,GCD有點像NSOperationQueue,他們都允許程式將任務切分為多個單一任務然後提交至工作佇列來併發地或者序列地執行。

    Druid.io系列基本概念架構

    在介紹Druid架構之前,我們先結合有關OLAP的基本原理來理解Druid中的一些基本概念。 1 資料 以圖3.1為例,結合我們在第一章中介紹的OLAP基本概念,按列的型別上述資料可以分成以下三類: 時間序列(Timestamp),Druid既是記憶

    iOS/OS X記憶體管理()基本概念原理

    在Objective-C的記憶體管理中,其實就是引用計數(reference count)的管理。記憶體管理就是在程式需要時程式設計師分配一段記憶體空間,而當使用完之後將它釋放。如果程式設計師對記憶體資源使用不當,有時不僅會造成記憶體資源浪費,甚至會導致程式crach。我們將會從引用計數和記憶體管理

    侯捷 C++記憶體管理

    本篇記錄 《侯捷 C++記憶體管理 》,整理各節的要點,以備查閱 1.Overview 2.記憶體分配的每一層面 3.四個層面的基本用法 1)、對比一下: 4.基本構件之一newdelete expression(上) ——》new和operator new、m

    初探jvm虛擬機器之記憶體管理

    本篇記錄一些概念性的東西,後續結合例項分析虛擬機器的記憶體機制。java虛擬機器在程式執行時將記憶體劃分為以下幾個區域,每個區域作用,生命週期各不相同程式計數器 虛擬機器棧 本地方法棧 方法區 堆程式計數器 執行緒執行的位元組碼行號指示器。 多執行緒是通過時間片輪轉法獲取cp

    c/c++ 動態記憶體管理

    C語言中 使用:malloc/calloc/realloc/free 進行動態記憶體管理 calloc:分配n個長度為size的連續記憶體空間,並初始為0 realloc:重新分配空間 void* realloc(void* p,size_t size)

    記憶體管理

    記憶體管理  記憶體分配的方式      記憶體分配方式有5種:        1)從靜態儲存區域分配。例如程式中定義的全域性變數和static變數就是這種方式分配記憶體的。記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。        2)在

    Linux虛擬記憶體管理

    分頁機制 虛擬記憶體—— 計算機的記憶體容量有限,而某些程序執行所需的記憶體空間可能超過記憶體總容量,因而出現機器記憶體容納不下該程序所有程式碼、資料和堆疊而只能容納其中一部分的情況。 虛擬儲存的基本思想:一個程序的程式碼、資料、堆疊的總容量可能超過可用實

    記憶體管理 ptmalloc基礎知識

        Top chunk對於主分配區和非主分配區的分配方式是不一樣的。對於非主分配區,會預先從mmap區銀河一塊較大記憶體模擬sub_heap,通過管理sub_heap來響應使用者請求。因為記憶體是按地址從低向高進行分配的,在空閒記憶體的最高處一定會存在著一塊空閒的chunk,叫做top chunk。當b

    iOS/OS X 記憶體管理(二)藉助工具解決記憶體問題

    這篇我們主要關注在實際開發中會遇到哪些記憶體管理問題,以及如何使用工具來除錯和解決。 在往下看之前請下載例項MemoryProblems,我們將以這個工程展開如何檢查和解決記憶體問題。 懸掛指標問題 懸掛指標(Dangling Pointer

    Java記憶體管理

    好久沒有寫部落格了,深感慚愧,今天聊一下Java的記憶體管理 簡介 Java相比傳統語言(C,C++)的一個優勢在於其能夠自動管理記憶體,從而將開發者管理記憶體任務剝離開來。 本文大體描述了J2SE 5.0 release中JVM對於記憶體是如何管理的。並

    黑馬程式設計師——OC基礎——記憶體管理

    一,為什麼要進行記憶體管理 1,由於移動裝置的記憶體有限,所以每個APP所佔的記憶體也是有限制的,當APP所佔用的記憶體較多時,系統就會發出警告,這時就需要回收一些不需要繼續使用的記憶體空間,比如回收一些不再使用的物件和變數等。 任何繼承NSObject的物件,對其他的基