自動(dòng)釋放池塊提供了一種機(jī)制,你可以借助這種機(jī)制放棄對(duì)象的所有權(quán),但又不會(huì)造成對(duì)象的立即銷毀(例如當(dāng)你從一個(gè)方法中返回對(duì)象時(shí))。一般情況下,你不需要?jiǎng)?chuàng)建自己的自動(dòng)釋放池塊,但也存在一些你不得不創(chuàng)建,或最好創(chuàng)建的情況。
關(guān)于自動(dòng)釋放池塊
自動(dòng)釋放池塊就是被@autoreleasepool標(biāo)記的代碼塊。就像下面這樣:
@autoreleasepool {
// Code that creates autoreleased objects.
}
在自動(dòng)釋放池塊的末尾,所有收到過(guò)autorelease消息的對(duì)象的release方法會(huì)被調(diào)用——在代碼塊中所有收到autorelease消息的對(duì)象都會(huì)收到release消息。
就像其他的代碼塊一樣,自動(dòng)釋放池塊可以嵌套:
@autoreleasepool {
// . . .
@autoreleasepool {
// . . .
}
. . .
}
(這種代碼并不常見(jiàn),通常情況是位于某代碼文件中自動(dòng)釋放池塊內(nèi)的代碼會(huì)調(diào)用另一個(gè)代碼文件中不同的自動(dòng)釋放池塊內(nèi)的代碼。)當(dāng)你在某個(gè)自動(dòng)釋放池塊里給某個(gè)對(duì)象發(fā)送了autorelease消息,那么對(duì)應(yīng)的release消息便會(huì)在這個(gè)自動(dòng)釋放池塊的末尾發(fā)送給該對(duì)象。
Cocoa總是期望你的代碼會(huì)放在一個(gè)自動(dòng)釋放池塊內(nèi)運(yùn)行,否則本該自動(dòng)釋放的對(duì)象不會(huì)被釋放,你的應(yīng)用將會(huì)有內(nèi)存泄漏。(如果你在一個(gè)自動(dòng)釋放池塊的外邊給對(duì)象發(fā)送了autorelease消息,Cocoa將會(huì)記錄一條對(duì)應(yīng)的錯(cuò)誤信息。)AppKit和UIKit框架在每個(gè)事件循環(huán)迭代(例如鼠標(biāo)或手指的輕點(diǎn))過(guò)程中處理一次自動(dòng)釋放池塊。所以通常你不需要親手創(chuàng)建一個(gè)自動(dòng)釋放池塊,或者說(shuō)真正看到用來(lái)創(chuàng)建自動(dòng)釋放池塊的代碼。但以下三種情況則可能會(huì)需要你使用自己的自動(dòng)釋放池塊:
- 如果你所寫的程序不是基于UI框架的時(shí)候,比如控制臺(tái)工具。
- 如果你寫的循環(huán)中包含了大量臨時(shí)對(duì)象的創(chuàng)建。
你可能會(huì)在循環(huán)代碼塊內(nèi)使用自動(dòng)釋放池塊,從而在下次循環(huán)進(jìn)行之前釋放這些對(duì)象。在循環(huán)中使用自動(dòng)釋放池塊會(huì)幫助改善應(yīng)用程序的最高內(nèi)存占用率。 - 如果你使用了多個(gè)線程。
你必須在線程開始運(yùn)行之前創(chuàng)建自己的自動(dòng)釋放池塊;否則,你的應(yīng)用程序?qū)?huì)內(nèi)存泄漏。(詳細(xì)討論參考下文自動(dòng)釋放池塊與線程。)
使用局部自動(dòng)釋放池塊降低最大內(nèi)存占用率
一些程序會(huì)創(chuàng)建大量自動(dòng)釋放的臨時(shí)對(duì)象。這些對(duì)象會(huì)在代碼塊結(jié)束之前增加程序的內(nèi)存占用率。有時(shí)候,在當(dāng)前時(shí)間循環(huán)迭代結(jié)束之前累積臨時(shí)變量不會(huì)導(dǎo)致內(nèi)存資源的過(guò)分消耗,但也有時(shí)候,巨大數(shù)量的臨時(shí)對(duì)象會(huì)占據(jù)客觀的內(nèi)存資源,這時(shí)你可能會(huì)希望對(duì)象盡可能快地被銷毀。在后者的情況下,你可能會(huì)需要?jiǎng)?chuàng)建自己的自動(dòng)釋放池塊。在代碼塊的末尾,這些臨時(shí)對(duì)象會(huì)被釋放,通常這些對(duì)象的釋放會(huì)降低程序的內(nèi)存使用率。
下面的例子展示了在for循環(huán)中使用自動(dòng)釋放池塊的例子:
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
/* Process the string, creating and autoreleasing more objects. */
}
}
for循環(huán)每次循環(huán)處理一個(gè)文件。所有的對(duì)象(例如fileContents)在自動(dòng)釋放池塊內(nèi)會(huì)收到一條autorelease消息,從而在自動(dòng)釋放池塊的末尾被釋放。
在自動(dòng)釋放池塊之后,你應(yīng)該意識(shí)到所有代碼塊內(nèi)的對(duì)象都被“釋放掉”了。不要給這些對(duì)象發(fā)送消息,或?qū)⑺鼈兎祷亟o當(dāng)前方法的調(diào)用者。如果你確實(shí)需要在自動(dòng)釋放池塊之外使用一個(gè)臨時(shí)對(duì)象,可以在代碼塊內(nèi)給該對(duì)象發(fā)送一條retain消息,并在代碼塊之外再給它發(fā)送autorelease消息。示例如下:
– (id)findMatchingObject:(id)anObject {
id match;
while (match == nil) {
@autoreleasepool {
/* 進(jìn)行一次搜索,從而產(chǎn)生大量臨時(shí)對(duì)象。 */
match = [self expensiveSearchForObject:anObject];
if (match != nil) {
[match retain]; /* 保持match對(duì)象。 */
}
}
}
return [match autorelease]; /* 返回match并使其自動(dòng)釋放 */
}
在自動(dòng)釋放池塊內(nèi)給match發(fā)送retain消息,并在自動(dòng)釋放池塊外邊給它發(fā)送autorelease消息。這擴(kuò)展了match的生存空間,并使其可以在返回給findMatchingObject:方法的調(diào)用者后可以被正常使用。
自動(dòng)釋放池塊與線程
<p id="jump1"></p>
Cocoa應(yīng)用程序的每個(gè)線程都維持它們自己的自動(dòng)釋放池塊棧。如果你正在寫一個(gè)只使用了基礎(chǔ)類庫(kù)的程序,或者當(dāng)你在分發(fā)一個(gè)新的線程的時(shí)候,你需要?jiǎng)?chuàng)建自己的自動(dòng)釋放池塊。
如果你的程序或者線程是長(zhǎng)時(shí)間活躍的,并且有可能會(huì)生成大量自動(dòng)釋放對(duì)象,那么你應(yīng)該使用自動(dòng)釋放池塊(就像AppKit和UIKit在主線程中所做的那樣);否則,自動(dòng)釋放對(duì)象會(huì)累積,使內(nèi)存占用率持續(xù)增加。如果你分發(fā)的線程里沒(méi)有調(diào)用Cocoa的方法,那么你不需要使用自動(dòng)釋放池塊。
注意: 如果你使用POSIX線程API來(lái)創(chuàng)建次要線程,而不是使用NSThread的話,除非Cocoa處在多線程模式,否則你不能使用Cocoa。Cocoa只有在分發(fā)了第一個(gè)NSThread對(duì)象后才會(huì)進(jìn)入多線程模式。為了在POSIX次要線程中使用Cocoa,你的程序必須先分發(fā)至少一個(gè)NSThread線程對(duì)象——這個(gè)線程可以立即退出。你可以調(diào)用NSThread的類方法isMultiThreaded來(lái)判斷Cocoa是不是處在多線程模式。