如何避免iPhone應(yīng)用中內(nèi)存泄露
來源:易賢網(wǎng) 閱讀:919 次 日期:2014-11-04 09:26:58
溫馨提示:易賢網(wǎng)小編為您整理了“如何避免iPhone應(yīng)用中內(nèi)存泄露”,方便廣大網(wǎng)友查閱!

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

所有權(quán)的背后是一個和引用有關(guān)的運算系統(tǒng),iPhone SDK的大多數(shù)對象使用這個系統(tǒng),彼此之間建立著很強的引用和參照。

當你創(chuàng)建一個對象時,引用值為1,調(diào)用一次retain則對象的引用值加1,調(diào)用一次release則對象的引用值減1,當引用值為0時,對象的所有權(quán)分配將被取消。使用自動釋放池意味著對象的所有權(quán)將在一段延后的時間內(nèi)被自動取消。

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

什么時候使用retain?

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

每當使用copy、alloc、retain、或者Cocoa函數(shù)來創(chuàng)建和復(fù)制所有權(quán),你都需要相應(yīng)的release或者auto-release。

開發(fā)者應(yīng)該從所有權(quán)的角度來考慮對象,而不必擔心引用值。只要你有相應(yīng)的retain和release方法,就能夠?qū)σ弥颠M行+1和-1操作。

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

自動釋放

將對象設(shè)置為自動釋放意味著不需要明確地請求釋放,因為當自動釋放池清空時它們將被自動釋放。iPhone在主線程上運行自動釋放池,能夠在事件循環(huán)結(jié)束后釋放對象。當你創(chuàng)建你自己的線程時,你需要創(chuàng)建自己的自動釋放池。

iPhone上有便利的構(gòu)造函數(shù),用這種方法創(chuàng)建的對象會設(shè)置為自動釋放。

例子:

NSString* str0 = @"hello";

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

NSString* str2 = str1;

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

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

[str autorelease];

或者用下面的方法:

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

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

在一個事件循環(huán)結(jié)束時,自動釋放池內(nèi)的構(gòu)件通常會被清空。但是當你的循環(huán)每次迭代都分配大量內(nèi)存時,你或許希望這不要發(fā)生。這種情況下,你可以在循 環(huán)內(nèi)創(chuàng)建自動釋放池。自動釋放池可以嵌套,所以內(nèi)部池清空時,其中分配的對象將被釋放。在下面的例子中,每次迭代后將釋放對象。

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的功能相同。當你想為程序設(shè)置OSX的端口時通常會使用drain,除非后來在iPhone中添加了垃圾回收機制。Drain能夠擊發(fā)垃圾回收器釋放內(nèi)存。

返回一個對象的指針

開發(fā)者在遵循所有權(quán)規(guī)則時需要清楚哪些函數(shù)擁有對象的所有權(quán)。下面是返回一個對象的指針并釋放的例子。

錯誤的方法:

- (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內(nèi)存管理指南中的規(guī)則,盡管它不會泄露內(nèi)存但是這樣做不好,因為Test 不應(yīng)該釋放并非它所擁有的對象。

正確的方法:

- (NSMutableString*) GetOutput

{

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

return [output autorelease];

}

- (void) Test

{

NSMutableString* obj = [self GetOutput];

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

}

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

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

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

Setters

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

在函數(shù)里:

- (void) setName:(NSString*)newName

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

[name release];

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

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

[newName retain];

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

現(xiàn)在我們設(shè)置 name 指向 newName 對象:

name = newName;

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

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

[newName retain];

[name release];

name = newName;

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

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

聲明如下:

@property(nonatomic, retain) NSString *name;

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

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

我們可以使用 copy 代替 retain:

@property(nonatomic, copy) NSString *name;

這和下面的函數(shù)一樣:

- (void) setName:(NSString*)newName

{

NSString* copiedName = [newName copy];

[name release];

name = copiedName;

[name retain];

[copiedName release];

}

newName 在這里被復(fù)制到 copiedName,現(xiàn)在 copiedName 擁有串的一個副本。name 被釋放,而 copiedName 被賦給 name。之后 name 保留這個串,從而使得 copiedName 和 name 同時擁有它。最后 copiedName 釋放這個對象,name 成為這個串的唯一所有者。

如果我們有如下的函數(shù),像這樣的 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 在調(diào)用至 drain 后是未定義的,因為當池被釋放時,name 也將被釋放。

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

[self setName:[self GetOutput]];

然后 name 將被這個類所有,在使用時保留直到調(diào)用 release

那么我們何時釋放對象?

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

- (void)dealloc

{

[name release];

[super dealloc];

}

注意:雖然并不總是調(diào)用 dealloc,依靠 dealloc 來釋放對象可能是危險,可能會觸發(fā)一些想不到的事情。在出口處,iPhone OS 可能在調(diào)用 dealloc 前清空全部應(yīng)用程序的內(nèi)存。

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

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

name 的設(shè)置是正確的但 alloc 沒有相應(yīng)的釋放,下面的方式要好一些:

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

[self setName:s];

[s release];

或者使用自動釋放:

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

自動釋放池

自動釋放池釋放位于分配和 drain 函數(shù)之間的對象。

我們在下面的函數(shù)中設(shè)置一個循環(huán),在循環(huán)中將 NSNumber 的一個副本賦給 magicNumber,另外將 magicNumber 設(shè)置為自動釋放。在這個例子中,我們希望在每次迭代時清空自動釋放池(這樣可以在賦值的數(shù)量很大時節(jié)省循環(huán)的內(nèi)存)

- (void) Test

{

NSString* clientName = nil;

NSNumber* magicNumber = nil;

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

{

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

magicNumber = [[self GetMagicNumber] copy];

[magicNumber autorelease];

if (i == [magicNumber intValue])

{

clientName = [self GetOutput];

}

[pool drain];

}

if (clientName != nil)

{

NSLog(@"Client Name: %@", clientName);

}

}

這里存在的問題是 clientName 在本地的自動釋放池中被賦值和釋放,所以當外部的池清空時,clientName 已經(jīng)被釋放了,任何對 clientName 的進一步使用都是沒有定義的。

在這個例子中,我們在賦值后保留 clientName,直到結(jié)束時再釋放它:

- (void) Test

{

NSString* clientName = nil;

NSNumber* magicNumber = nil;

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

{

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

magicNumber = [[self GetMagicNumber] copy];

[magicNumber autorelease];

if (i == [magicNumber intValue])

{

clientName = [self GetOutput];

[clientName retain];

}

[pool drain];

}

if (clientName != nil)

{

NSLog(@"Client Name: %@", clientName);

[clientName release];

}

}

我們在調(diào)用 retain 函數(shù)和 release 函數(shù)的期間獲得 clientName 的所有權(quán)。通過添加一對 retain 和 release 的調(diào)用,我們就確保 clientName 在明確調(diào)用釋放前不會被自動釋放。

集合

當一個對象被添加進集合時,它就被集合所擁有。在這個例子中我們分配一個串,它現(xiàn)在有了所有者;

NSString* str = [[NSString alloc] initWithString:@"Bruce Wayne"];

然后我們將它添加進數(shù)組,現(xiàn)在它有兩個所有者:

[array addObject: str];

我們可以安全的釋放這個串,使其僅被數(shù)組所有:

[str release];

當一個集合被釋放時,其中的所有對象都將被釋放。

NSMutableArray* array = [[NSMutableArray alloc] init];

NSString* str = [[NSString alloc] initWithString:@"Bruce Wayne"];

[array addObject: str];

[array release];

在上面的例子中,我們分配了一個數(shù)組和一個串,然后將串添加到數(shù)組中并釋放數(shù)組。這使得串僅擁有一個所有者,并且在我們調(diào)用 [str release] 前它不會被釋放。

用線程傳遞指針

在這個函數(shù)中,我們從串的 input 傳遞到函數(shù) DoSomething,然后釋放 input

- (void) Test

{

NSMutableString* input = [[NSMutableString alloc] initWithString:@"batman!"];

[NSThread detachNewThreadSelector:@selector(DoSomething:) toTarget:self withObject:input];

[input release];

}

detatchNewThreadSelector 增加 input 對象的引用值并在線程結(jié)束時釋放它。這就是為什么我們能夠在線程剛開始的時候就釋放 input,而無論函數(shù) DoSomething 何時開始或結(jié)束。

- (void) DoSomething:(NSString*)str

{

[self performSelectorOnMainThread:@selector(FinishSomething:) withObject:str waitUntilDone:false];

}

performSeclectorOnMainThread 也會保留傳遞的對象,直到 selector 結(jié)束。

自動釋放池是特殊的線程,所以如果我們在一個新的線程上創(chuàng)建自動釋放的對象,我們需要創(chuàng)建一個自動釋放池來釋放它們。

[NSThread detachNewThreadSelector:@selector(Process) toTarget:self withObject:nil];

這里在另一個線程上調(diào)用函數(shù) Process

- (void) Process

{

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

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

NSLog(@"output: %@", output);

[self performSelectorOnMainThread:@selector(FinishProcess) withObject:nil waitUntilDone:false];

[pool drain];

}

對象 output 被分配并且在自動釋放池中設(shè)置了自動釋放,它將在函數(shù)結(jié)束前被釋放。

- (void) FinishProcess

{

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

NSLog(@"output: %@", output);

}

系統(tǒng)會為主線程自動創(chuàng)建一個自動釋放池,所以在 FinishProcess 中,我們不需要為主線程上運行的函數(shù)創(chuàng)建自動釋放池。

更多信息請查看IT技術(shù)專欄

更多信息請查看技術(shù)文章
易賢網(wǎng)手機網(wǎng)站地址:如何避免iPhone應(yīng)用中內(nèi)存泄露
關(guān)于我們 | 聯(lián)系我們 | 人才招聘 | 網(wǎng)站聲明 | 網(wǎng)站幫助 | 非正式的簡要咨詢 | 簡要咨詢須知 | 加入群交流 | 手機站點 | 投訴建議
工業(yè)和信息化部備案號:滇ICP備2023014141號-1 云南省教育廳備案號:云教ICP備0901021 滇公網(wǎng)安備53010202001879號 人力資源服務(wù)許可證:(云)人服證字(2023)第0102001523號
聯(lián)系電話:0871-65317125(9:00—18:00) 獲取招聘考試信息及咨詢關(guān)注公眾號:hfpxwx
咨詢QQ:526150442(9:00—18:00)版權(quán)所有:易賢網(wǎng)