Objective-C:內存管理

1 傳統內存管理 ide

      Objective-C對象的生命週期能夠分爲:建立、存在、消亡。 函數

1.1 引用計數 ui

      相似Java,Objective-C採用引用計算(reference counting)技術來管理對象的生命週期。每一個對象都定義有一個整數(稱引用計數器)與之相關聯,該數用以表示當前有多少個指針指向該對象spa

1.1.1 操做方法 3d

      當某段代碼須要訪問一個對象時,該代碼就將對象的保留計數值加1;當結束訪問時就減1;若引用計數器減到0時,該對象將被銷燬。引用計數器的值由以下三種操做進行控制: 指針

      1) 建立 code

          當使用allocnew方法或者經過copy消息(接收到消息的對象會建立一個自身的副本)建立一個對象時,對象的保留計數器值就被初始化爲1對象

      2) 增長 blog

          要增長對象的引用計數器值,能夠給對象發送一條retain消息,即調用對象的retain方法。 教程

      3) 減小

          要減小對象的引用計數器值,能夠給對象發送一條release消息,即調用對象的release方法。

 

       當一個對象因其引用計數器值爲0時,將被系統銷燬,從而系統自動給該對象發送一條dealloc消息。因此用戶能夠重載對象dealloc方法,dealloc方法至關是C++的虛構函數,能夠在該函數中釋放申請的內存空間。

表 11 NSObject類內存管理方法

方法

描述

- (instancetype)retain

將引用計數器的值加1,可由用戶調用。

- (oneway void)release

將引用計數器的值減1,可由用戶調用。

- (NSUInteger)retainCount

獲取引用計數器的值,可由用戶調用。

- (instancetype)autorelease

將對象添加到自動釋放池中,可由用戶調用。

- (struct _NSZone *)zone

複製方法。

 

      以下所示是RetainTracker對象生命週期的引用計數器值:

 1  int main ( int argc,  const  char * argv[])
 2 {
 3     RetainTracker * rt = [[RetainTracker alloc] init];
 4     NSLog( @" alloc:%d ",[rt retainCount]);
 5 
 6     [rt retain];
 7     NSLog( @" retain:%d ",[rt retainCount]);
 8     
 9     [rt release];
10     NSLog( @" release:%d ",[rt retainCount]);
11      return ( 0);
12 }  //  main

1.1.2 對象全部權

      對象全部權是指實體的一種職責,當某個實體"擁有一個對象"時,就意味着該實體要負責對其擁有的對象進行清理。實體可能擁有對象的狀況有:

  • 若是一個對象內由指向其餘對象的實例變量,則稱該對象擁有這些對象;

  • 若是一個函數建立了一個對象,則稱該函數擁有這個對象。

1.1.3 訪問方法

      將類中的成員指針設置爲指向一個外部對象,需經過retain和release方法來操做引用計數值,以下有3種操做方式:

1) 簡單賦值

    簡單賦值方式,以下所示:

1 -( void) setEngine:(Engine*)newEngine
2 {
3      engine = [newEngine retain];
4 }
5 這種方式只增長引用計數值,而未減小計數值。致使當再次調用setEngine方法時,未減小原來成員指針的引用計數值。

2) 修復賦值

    這種方式是對前一種方式的修復,即修復了未對原來成員變量的引用計數值進行減小操做,但仍存在問題,以下所示:

1 -( void) setEngine:(Engine*)newEngine
2 {
3      [engine release];             // 先減小引用計數器值
4       engine = [newEngine retain];   // 再增長引用計數器值
5  }
6 若newEngine和engine是同一個對象,而且引用計數值爲1;那麼當調用setEngine方法時,在調用release後,會銷燬該對象,從而當接着調用retain後會報錯。

3) 正確賦值

    這種方式是正確的賦值方式,修復了前兩種錯誤方式。即修復了未對原來成員指針的引用計數值操做,也修復了可能出現同一個指針的問題,以下所示:

1 -( void) setEngine:(Engine*)newEngine
2 {
3 [newEngine retain];     // 先增長引用計數器值
4  [engine release];       // 再減小引用計數器值
5  engine = newEngine;
6 }
7 這種方式保障了引用計數器值必定大於1,不會出現爲0的狀況。

 

1.2 自動釋放池

       內存管理是一個棘手的問題,如上述所示的setter方法的各類細微問題。因此Cocoa引入了自動釋放池(autorelease pool)概念,這種方式是經過自動釋放池來管理引用計數值的release操做。有兩種方式建立自動釋放池:

  • @autoreleasepool關鍵字
  • NSAutoreleasePool對象

1.2.1 使用方式

      若要使用自動釋放池來管理對象的release操做,只要在自動釋放池的生命週期內調用被管理對象autorelease方法,便可將對象的引用計數值委託自動釋放池實體來管理,當自動釋放池實體結束時,將會調用池中對象release方法,而且只調用一次。以下所示:

 1 -( void) autorelease
 2 {
 3     RetainTracker * rt = [[RetainTracker alloc] init];
 4     [rt retain];
 5     [rt retain];
 6      NSLog( @" before:%d ",[rt retainCount]);
 7     
 8     @autoreleasepool {
 9         [rt autorelease];
10     }
11     
12     NSLog( @" after:%d ",[rt retainCount]);
13 }
14  2016- 02- 07  10: 52: 36.920 ObjectC[ 725: 41864] before: 3
15  2016- 02- 07  10: 52: 36.921 ObjectC[ 725: 41864] after: 2
16 Program ended with exit code:  0

1) 關鍵字方式

      @autoreleasepool方式的生命週期是從左花括號""開始,直到右花括號""結束,即執行到了右花括號時,那麼將調用自動釋放池中對象的release方法來減小相應的引用計數值。

2) 對象方式

     NSAutoreleasePool對象的生命週期是從調用其new方法建立對象開始,直到調用池對象release方法後,由系統調用釋放池對象的dealloc方法後結束,即自動釋放池在dealloc"虛構函數"中調用被管理對象的release方法來減小相應引用計數值。

1.2.2 釋放池結構

       自動釋放池以棧的形式實現:當建立了一個新的自動釋放池時,該池就被添加到棧頂中。因此若某個對象調用autorelease方法時,該對象將被放入最頂端(棧頂)自動釋放池中,而且棧頂下釋放池仍未被銷燬,即被添加到棧頂下面釋放池的對象仍未被釋放

      若須要由自動釋放池來管理大量對象時,用戶能夠手動銷燬釋放池,而後再建立新的池對象,以下所示:

 1 NSAutoreleasePool *pool;
 2 Pool = [[NSAutoreleasePool alloc] init];
 3  int i;
 4  for(i =  0; i< 1000000;i++)
 5 {
 6 id  object = [someArray objetcAtIndex: i];
 7 [id autorelease];
 8      NSString *desc = [ object description];
 9       if(i% 1000)
10      {
11          [pool release];
12          pool = [[NSAutoreleasePool alloc] init];
13      }
14 }
15 [pool release];

      Cocoa也使用相似的方法來管理內存,當使用AppKit時,Cocoa會按期自動地爲用戶建立和銷燬自動釋放池。一般是在程序處理當前事件(如鼠標單擊或鼠標按下)之後執行這些操做。

1.3 Cocoa內存管理規則

         Cocoa有許多內存管理約定,它們簡化了retain、release和autorelease的使用方法,這些規則有:

  • 當使用newalloccopy方法建立一個對象時,該對象引用計數值將被初始化1當再也不使用該對象時應該向該對象發送一條releaseautorelease消息。這樣,該對象將在其使用壽命結束時被銷燬。
  • 當經過其餘方法得到一個對象時,假設該對象的引用計數值爲1,並且已經被設置爲自動釋放,那麼用戶不須要執行任何操做來確保該對象獲得清理。若是打算在一段時間內擁有該對象,則須要保留它並確保在操做完成時釋放它。

2 自動引用計數

      自動引用計數爲Automatic Reference Counting (ARC) ,是Objective-C提供了一種自動內存管理的功能。ARC不須要用戶考慮retain和release操做,而是在編譯期添加代碼(retain和release等方法)來保障對象的生命週期,同時可爲對象自動生成合適的dealloc方法。從而讓用戶專一那些感興趣的代碼。以下是引用計數器手動和自動的差別:

圖 21 引用計數器的手動和自動實現的差別

    

      自動的引用計數和手動引用計數方法是互斥的,即不能同時在應用程序中使用ARC技術和手動操做retain和release。如在圖 22所示,若選擇YES時(默認),則啓動ARC功能;若NO,則關閉ARC功能。

圖 22 ARC功能啓動設置

2.1 強制規則

      爲了ARC能工做,強制規定了一些新規則,而且這些規則不能在其它編譯器使用。若是用戶違反了這些規則,那麼將獲得一個compile-time錯誤。這些規則爲:

      1) 不能顯示調用dealloc方法,同時不能調用和重載retainreleaseretainCountautorelease方法。但能夠重載dealloc方法,固然在dealloc方法中不能調用引用計數器管理方法,同時在dealloc方法中不能調用 [super dealloc],父類的dealloc方法由編譯器自動添加。

      2) 不可以使用NSAllocateObject和NSDeallocateObject,但仍可以使用alloc方法建立對象。

      3) 不可以使用NSAutoreleasePool對象,但可使用@autoreleasepool代碼塊

      4) 屬性名次不能以new開頭,好比說@property NSString *newString是不容許的。

      5) 屬性不能只有一個read-only而沒有其它內存管理特性。若沒有啓用ARC功能,可使用@property (readonly) NSString *title語句,但若是啓用來ARC功能,就必須指定由誰來管理內存。

      6) 結構體(struct)和聯合體(union)不能使用ROP做爲成員,如struct {NSString *str}代碼是不被容許.

ARC只對可保留的對象指針(ROPs)有效。可保留的對象指針主要有以下三種:

  • 代碼塊指針;
  • Objective-C對象指針;
  • 經過_attribute_((NSObject))類型定義的指針。

    全部其它指針類型,好比char*和CF對象都不支持ARC特性,若是使用的指針不支持ARC,那麼必須手動管理這些對象空間。

2.2 變量修飾詞

2.2.1 保留環

      使用引用計數機制時,常常要注意的一個問題是"保留環"retain cycle,即呈環狀相互引用的多個對象。這將致使內存泄漏,由於循環中的對象其保留計數不會降爲0。對於循環中的每一個對象來講,至少還有另外一個對象引用着它。

      如圖 23所示,A的引用計數爲2,而B的引用計數爲1。當A的擁有者"雲"release A後,A和B的引用計數都爲1,致使二者都沒法被釋放。

圖 23 引用計數保留環

 

2.2.2 修飾詞

      目前Objective-C提供4種修飾詞(qualifier)來修飾變量,具體語義爲:

表 21 Objective-C修飾詞

類型

語義

__strong

默認修飾詞,當有一個strong指針指向某對象,那麼該對象將一直保持"活躍"狀態;

__weak

其不能讓被引用對象一直保持"活躍"狀態。當某個被引對象沒有__strong指針指向它時,那麼其它對象以__weak類型指向上述對象的指針將被自動置爲nil。

__unsafe_unretained

聲明一個弱應用,可是不會自動nil化,也就是說,若是所指向的內存區域被釋放了,這個指針就是一個野指針了。

__autoreleasing

用來修飾一個函數的參數,這個參數會在函數返回的時候被自動釋放。

 

    其使用形式爲:

1 ClassName * qualifier variableName;
2 eg:
3      MyClass * __weak myWeakReference;

      對於圖 23所示的引用環能夠採用弱引用解決,由於在指向的對象釋放以後,這些弱引用就會被設置爲nil。如圖 24所示,帶有__weak的引用環結構,當"雲"向A發送release消息後,A的引用計數爲0,從而釋放A和B對象內存空間。

圖 24 帶有弱引用的環

2.3 Toll-Free Bridging管理

      ARC僅支持可保留對象指針,而沒法自動管理Core Foundation對象的空間,必須由用戶手動調用CFRetain 和 CFRelease方法來管理Core Foundation對象。若是在Objective-C 和 Core Foundation-style 對象之間進行轉換時,爲了讓ARC便於工做,那麼須要告訴編譯器哪一個對象是指針的擁有者。爲此Objective-C使用了橋接轉換(bridged cast)技術。

1) __bridge類型操做符

    這種操做符是ROP類型轉換non-ROP類型,但只傳遞指針並不會傳遞它的全部權,即指針的全部權仍在轉換以前的對象上。以下所示:

1 NSString *theString =  @" hello world ";
2 CFStringRef cfString = (__bridge CFStringRef)theString;
3 cfString接收了指令,但指針的全部權仍然由theString保留

2) __bridge_retained類型操做符

    這種操做符將指針全部權ROP轉移到non-ROP。由於ARC只會注意到non-ROP,因此用戶須要經過non-ROP手動釋放其保留計數器的值。這個轉換類型會給non-ROP對象的保留計數器加1,因此須要手動讓它減1,這與標準的內存管理方式相同。以下所示:

1 NSString *theString =  @" hello world ";
2 CFStringRef cfString = (__bridge_retained CFStringRef)theString;
3 cfString對象擁有指針而且它的保留計數爲1,須要使用CFRetain和CFRelease來管理它的內存

3) __bridge_transfer類型操做符

    這種轉換符與上一個相反,它將全部權non-ROP轉移到ROP上,從而ARC擁有對象並能確保它會像其它ARC對象同樣獲得釋放。

 

3 參考文獻

      [1] Advanced Memory Management programming guide

      [2] Transitioning to ARC Release Notes

      [3] Objective-C基礎教程(第2版)

      [4] Effective Objective-C 2.0

相關文章
相關標籤/搜索