萬盛學電腦網

 萬盛學電腦網 >> 網絡編程 >> ios >> 如何避免iPhone應用中內存洩露

如何避免iPhone應用中內存洩露

   創建對象時,所有權通過alloc、new、或者copy的方式建立,之後通過調用retain或者通過Cocoa函數來分配和復制對象的所有權。 內存釋放有兩種方式,一種方法是明確地請求釋放對象的所有權,另一種方法則是使用自動釋放池(auto-release pool)。

  所有權的背後是一個和引用有關的運算系統,iPhone SDK的大多數對象使用這個系統,彼此之間建立著很強的引用和參照。

  當你創建一個對象時,引用值為1,調用一次retain則對象的引用值加1,調用一次release則對象的引用值減1,當引用值為0時,對象的所有權分配將被取消。使用自動釋放池意味著對象的所有權將在一段延後的時間內被自動取消。

  對象之間也可以建立弱的引用參照,此時意味著,引用值不會被保留,對象的分配需要手動取消。

  什麼時候使用retain?

  什麼時候你想阻止對象在使用前就被釋放?

  每當使用copy、alloc、retain、或者Cocoa函數來創建和復制所有權,你都需要相應的release或者auto-release。

  開發者應該從所有權的角度來考慮對象,而不必擔心引用值。只要你有相應的retain和release方法,就能夠對引用值進行+1和-1操作。

  注意:你或許想使用[object retainCount],但它可能因為SDK的底層代碼而發生返回值出錯的情況。在內存管理時不推薦這種方式。

  自動釋放

  將對象設置為自動釋放意味著不需要明確地請求釋放,因為當自動釋放池清空時它們將被自動釋放。iPhone在主線程上運行自動釋放池,能夠在事件循環結束後釋放對象。當你創建你自己的線程時,你需要創建自己的自動釋放池。

  iPhone上有便利的構造函數,用這種方法創建的對象會設置為自動釋放。

  例子:

  NSString* str0 = @"hello";

  NSString* str1 = [NSString stringWithString:@"world"];

  NSString* str2 = str1;

  一個已分配的對象可以用如下的方法設置為自動釋放:

  NSString* str = [[NSString alloc] initWithString:@"the flash?"];

  [str autorelease];

  或者用下面的方法:

  NSString* str = [[[NSString alloc] initWithString:@"batman!"] autorelease];

  當指針出界,或者當自動釋放池清空時,自動釋放對象上的所有權將被取消。

  在一個事件循環結束時,自動釋放池內的構件通常會被清空。但是當你的循環每次迭代都分配大量內存時,你或許希望這不要發生。這種情況下,你可以在循 環內創建自動釋放池。自動釋放池可以嵌套,所以內部池清空時,其中分配的對象將被釋放。在下面的例子中,每次迭代後將釋放對象。

  for (int i = 0; i < 10; ++i)

  {

  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  NSString* str = [NSString stringWithString:@"hello world"];

  [self ProcessMessage: str];

  [pool drain];

  }

  注意:在編寫的時候iPhone不支持垃圾回收,所以drain和release的功能相同。當你想為程序設置OSX的端口時通常會使用drain,除非後來在iPhone中添加了垃圾回收機制。Drain能夠擊發垃圾回收器釋放內存。

  返回一個對象的指針

  開發者在遵循所有權規則時需要清楚哪些函數擁有對象的所有權。下面是返回一個對象的指針並釋放的例子。

  錯誤的方法:

  - (NSMutableString*) GetOutput

  {

  NSMutableString* output = [[NSMutableString alloc] initWithString:@"output"];

  return output;

  }

  - (void) Test

  {

  NSMutableString* obj = [self GetOutput];

  NSLog(@"count: %d", [obj retainCount]);

  [obj release];

  }

  在這個例子中,output 的所有者是 GetOutput,讓 Test 釋放 obj 違反了Coccoa內存管理指南中的規則,盡管它不會洩露內存但是這樣做不好,因為Test 不應該釋放並非它所擁有的對象。

  正確的方法:

  - (NSMutableString*) GetOutput

  {

  NSMutableString* output = [[NSMutableString alloc] initWithString:@"output"];

  return [output autorelease];

  }

  - (void) Test

  {

  NSMutableString* obj = [self GetOutput];

  NSLog(@"count: %d", [obj retainCount]);

  }

  在第二個例子中,output 被設置為當 GetOutput 返回時自動釋放。output的引用值減少,GetObject 釋放 output 的所有權。Test 函數現在可以自由的 retain 和 release 對象,請確保它不會洩露內存。

  例子中 obj 被設置為自動釋放,所以 Test 函數沒有它的所有權,但是如果它需要在其他地方存儲對象會怎樣?

  此時對象需要有一個新的所有者來保留。

  Setters

  setter函數必須保留它所存儲的對象,也就是聲明所有權。如果我們想要創建一個 setter 函數,我們需要在分配一個新的指向成員變量的指針之前做兩件事情。

  在函數裡:

  - (void) setName:(NSString*)newName

  首先我們要減少成員變量的引用值:

  [name release];

  這將允許當引用值為0時 name 對象被釋放,但是它也允許對象的其他所有者繼續使用對象。

  然後我們增加新的 NSString 對象的引用值:

  [newName retain];

  所以當 setName 結束時, newName 不會被取消分配。 newName 現在指向的對象和 name 指向的對象不同,兩者有不同的引用值。

  現在我們設置 name 指向 newName 對象:

  name = newName;

  但是如果 name 和 newName 是同一個對象時怎麼辦?我們不能在它被釋放後保留它,並再次釋放。

  在釋放存儲的對象前保留新的對象:

  [newName retain];

  [name release];

  name = newName;

  現在兩個對象是相同的,先增加它的引用值,然後再減少,從而使得賦值前引用值不變。

  另一種做法是使用 objective-c:

  聲明如下:

  @property(nonatomic, retain) NSString *name;

  1. nonatomic 表示沒有對同一時間獲取數據的多個線程進行組塊兒。Atomic 為一個單一的線程鎖定數據,但因為 atomic 的方式比較緩慢,所以不是必須的情況一般不使用。

  2. retain 表示我們想要保留 newName 對象。

  我們可以使用 copy 代替 retain:

  @property(nonatomic, copy) NSString *name;

  這和下面的函數一樣:

  - (void) setName:(NSString*)newName

  {

  NSString* copiedName = [newName copy];

  [name release];

  name = copiedName;

  [name retain];

  [copiedName release];

  }

  newName 在這裡被復制到 copiedName,現在 copiedName 擁有串的一個副本。name 被釋放,而 copiedName 被賦給 name。之後 name 保留這個串,從而使得 copiedName 和 name 同時擁有它。最後 copiedName 釋放這個對象,name 成為這個串的唯一所有者。

  如果我們有如下的函數,像這樣的 setters 將被輸入用來保留成員對象:

  - (void) Test

  {

  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  // do something...

  name = [self GetOutput];

  // do something else...

  NSLog(@"Client Name before drain: %@", name);

  [pool drain];

  NSLog(@"Client Name after drain: %@", name);

  }

  name 在調用至 drain 後是未定義的,因為當池被釋放時,name 也將被釋放。

  如果我們用如下的部分替代賦值:

  [self setName:[self GetOutput]];

  然後 name 將被這個類所有,在使用時保留直到調用 release

  那麼我們何時釋放對象?

  由於 name 是成員變量,釋放它的最安全的辦法是對它所屬的類使用 dealloc 函數。

  - (void)dealloc

  {

  [name release];

  [super dealloc];

  }

  注意:雖然並不總是調用 dealloc,依靠 dealloc 來釋放對象可能是危險,可能會觸發一些想不到的事情。在出口處,iPhone OS 可能在調用 dealloc 前清空全部應用程序的內存。

  當用 setter 給對象賦值時,請小心下面的語句:

  [self setName:[[NSString alloc] init]];

  name 的設置是正確的但 alloc 沒有相應的釋放,下面的方式要好一些:

  NSString* s = [[NSString alloc] init];

  [self setName:s];

  [s release];

  或者使用自動釋放:

  [self setName:[[[NSString alloc] init] autorelease]];

  自動釋放池

  自動釋放池釋放位於分配和 drain 函數之間的對象。

  我們在下面的函數中設置一個循環,在循環中將 NSNumber 的一個副本賦給 magicNumber,另外將 magicNumber 設置為自動釋放。在這個例

copyright © 萬盛學電腦網 all rights reserved