高級內(nèi)存管理編程指南

1.簡介

應用程序的內(nèi)存管理就是在程序的運行期開辟內(nèi)存,使用內(nèi)存,使用完畢后釋放內(nèi)存。一個好的程序使用盡可能少的內(nèi)存。在Objective-C中,內(nèi)存管理可以認為是一種分配有限內(nèi)存資源的所有權(quán)給多種數(shù)據(jù)和代碼的方式。當你讀完這篇文章時,你應該具備管理你程序內(nèi)存的知識,明確地管理對象的生命周期并在對象不在需要時釋放它們。
盡管內(nèi)存管理通常只在個別對象的層次考慮,但是我們的目標是管理對象圖表。你應該確保內(nèi)存中不會有實際不需要的對象。

  • 對象圖表

概覽

Objective-C提供了兩種方式管理內(nèi)存

  • 本篇文章用到的方法,被稱為“手動持有-釋放”,英文為“manual retain-release”,或者叫MRR,通過追蹤你持有的對象來明確地管理內(nèi)存。它通過一種由NSObject提供的叫做引用計數(shù)的模型與運行時系統(tǒng)一同來實現(xiàn)。
  • 在自動引用計數(shù)(ARC)中,系統(tǒng)像MRR一樣使用同樣的引用計數(shù)系統(tǒng),只不過ARC在編譯階段幫你插入了合適的內(nèi)存管理方法調(diào)用。雖然現(xiàn)在很少使用MRR,但是了解MRR在某些情況下還是很有用的。

好的習慣可以避免內(nèi)存相關(guān)問題

錯誤的內(nèi)存管理會導致兩類主要的內(nèi)存問題

  • 釋放或者重寫仍在使用的數(shù)據(jù)
    這會導致內(nèi)存崩潰,通常會導致應用崩潰,更糟的會丟失用戶數(shù)據(jù)。
  • 不釋放不再使用的數(shù)據(jù)導致內(nèi)存泄漏
    內(nèi)存泄漏就是開辟的內(nèi)存不再使用時未被釋放。這會導致你的應用使用持續(xù)增加的內(nèi)存,反過來會導致低下的系統(tǒng)性能或者應用被終止。

但是,從引用計數(shù)的角度思考內(nèi)存管理通常達不到預期效果,因為你趨向于依據(jù)實現(xiàn)細節(jié)而不是實際目標來思考內(nèi)存管理。相反,應該從對象所有權(quán)和對象圖表的角度去思考內(nèi)存管理。

使用分析工具調(diào)試內(nèi)存問題

想在編譯階段找出代碼的內(nèi)存問題,可以使用Clang的靜態(tài)分析器,快捷鍵是:command + shift + B。
如果內(nèi)存管理問題依舊出現(xiàn),還有其它的工具和技術(shù)來幫助識別和診斷問題所在。

  • 許多工具和技術(shù)可以參考iOS Debugging Magic,尤其是使用NSZombie來查找過度釋放對象。
  • 使用Instruments來追蹤引用計數(shù)事件,尋找內(nèi)存泄漏,見Collecting Data on Your App


2.內(nèi)存管理策略

在一個引用計數(shù)的環(huán)境下內(nèi)存管理的基礎模型是由定義在NSObject協(xié)議的一組方法和一個標準的方法命名規(guī)則提供的。NSObject類也定義了一個dealloc方法,當一個對象被銷毀(deallocated)時這個方法會自動調(diào)起。本節(jié)描述了所有基本的規(guī)則,你需要了解這些規(guī)則來正確的管理Cocoa項目的內(nèi)存,并且提供了一些正確使用內(nèi)存的例子。

2.1.基本的內(nèi)存管理規(guī)則

內(nèi)存管理模型是基于對象所有權(quán)的。一個對象也許有一個或多個所有者(owners)。一個對象只要還擁有至少一個所有者,它就會繼續(xù)存活下去。如果沒有所有者,運行時系統(tǒng)會自動銷毀這個對象。為了明確何時你擁有一個對象,何時你不擁有一個對象,Cocoa設置了以下策略:

  • 你擁有任何你創(chuàng)建的對象
    使用以“alloc”, “new”, “copy”, 或者 “mutableCopy”開頭的方法創(chuàng)建一個對象(例如alloc,newObject或者mutableCopy)。
  • 使用retain可以獲得對象的所有權(quán)
    一個接受的對象通常在方法內(nèi)是可以保證持續(xù)有效的,方法可也以安全的將這個對象返回給它的調(diào)用者。在兩種情況下使用retain:1.在獲取方法的實現(xiàn)或者init方法來持有一個你想存儲為屬性的對象的所有權(quán);2.避免由于一些其它操作的副作用導致對象變得無效。
  • 當你不再需要一個對象時,你必須放棄你擁有的對象的所有權(quán)
    通過向一個對象發(fā)送release或者autorelease消息來放棄這個對象的所有權(quán)。以Cocoa術(shù)語,放棄一個對象的所有權(quán)通常被稱為釋放(“releasing”)一個對象。
  • 你不可以放棄一個你不擁有的對象的所有權(quán)
    這只是以上策略的推論。

一個簡單的例子

為了闡明以上策略,思考以下代碼片段:

Person *aPerson = [[Person alloc] init];
    // ...
    NSString *name = aPerson.fullName;
    // ...
    [aPerson release];

這個Person對象是通過alloc方法創(chuàng)建的,因此接下來當它不再需要時收到了一條release消息。但是注意,這個例子使用了release而不是autorelease

使用autorelease發(fā)送一條延遲釋放消息

使用autorelease來發(fā)送一條延遲釋放消息,通常用在從一個方法返回一個對象。例如,可以這樣實現(xiàn)fullName方法:

- (NSString *)fullName {
    NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@",
                                          self.firstName, self.lastName] autorelease];
    return string;
}

你擁有這個通過alloc返回的字符串。為了遵守內(nèi)存管理規(guī)則,你必須在失去對它的引用前放棄這個字符串的所有權(quán)。但是,如果使用release,這個字符串會在被返回前就被銷毀(這個方法會返回一個無效的對象)。使用autorelease意味著你想要放棄所有權(quán),但是你允許方法的調(diào)用者在返回值被銷毀前使用它。
也可以像下面這樣實現(xiàn)fullName方法:

- (NSString *)fullName {
    NSString *string = [NSString stringWithFormat:@"%@ %@",
                                 self.firstName, self.lastName];
    return string;
}

根據(jù)基礎規(guī)則,你不擁有通過stringWithFormat:返回的字符串,所以可以安全的從這個方法返回這個字符串。
相反,下面的實現(xiàn)就是錯誤的:

- (NSString *)fullName {
    NSString *string = [[NSString alloc] initWithFormat:@"%@ %@",
                                         self.firstName, self.lastName];
    return string;
}

根據(jù)命名規(guī)則,fullName方法的調(diào)用者并不擁有返回的字符串。調(diào)用者因此沒有理由去釋放這個返回值,進而導致內(nèi)存泄漏。

你不擁有通過引用返回的對象

Cocoa的一些方法指定一個對象是通過引用返回的,也就是說這個方法使用一個ClassName **或者id *類型的參數(shù)。一個通用的模式是使用一個NSError對象,它包含了錯誤的信息。
例如NSDatainitWithContentsOfURL:options:error:方法,或者NSStringinitWithContentsOfFile:encoding:error:方法。
在這些情況下,同樣的內(nèi)存管理規(guī)則是適用的。當你喚起任何這樣的方法,你并沒有創(chuàng)建NSError對象,因此你并不擁有它。因此也就沒必要去釋放它,像下面的例子這樣:

NSString *fileName = <#Get a file name#>;
NSError *error;
NSString *string = [[NSString alloc] initWithContentsOfFile:fileName
                        encoding:NSUTF8StringEncoding error:&error];
if (string == nil) {
    // Deal with error...
}
// ...
[string release];

2.2.實現(xiàn)dealloc來放棄對象的所有權(quán)

NSObject類定義了一個dealloc方法,當一個對象沒有所有者并且這個對象的內(nèi)存被回收時會自動調(diào)起這個方法--用Cocoa的術(shù)語就是這個對象被釋放或者銷毀了。dealloc方法的角色是釋放這個對象自己的內(nèi)存,清除任何它持有的資源,包括任何對象實例變量的所有權(quán)。
下面的例子演示了如何為一個Person類實現(xiàn)一個dealloc方法:

@interface Person : NSObject
@property (retain) NSString *firstName;
@property (retain) NSString *lastName;
@property (assign, readonly) NSString *fullName;
@end
 
@implementation Person
// ...
- (void)dealloc
    [_firstName release];
    [_lastName release];
    [super dealloc];
}
@end

重要:永遠不要直接調(diào)用另一個對象的dealloc方法。
必須在實現(xiàn)的最后調(diào)用父類的實現(xiàn)。
當程序終止時,對象也許不會收到dealloc消息。因為在退出時進程的內(nèi)存會自動被清理,讓操作系統(tǒng)來清理內(nèi)存要比調(diào)起所有的內(nèi)存管理方法高效的多。

2.3.Core Foundation適用相似但不同的規(guī)則

對于Core Foundation對象有相似的內(nèi)存管理規(guī)則,詳見Memory Management Programming Guide for Core Foundation。但是對于Cocoa和Core Foundation的命名規(guī)則是不同的。尤其是Core Foundation的創(chuàng)建規(guī)則(詳見The Create Rule)并不適用于返回Objective-C對象的方法。例如下面的代碼片段,你并不負責放棄myInstance的所有權(quán):

MyClass *myInstance = [MyClass createInstance];


3.實用的內(nèi)存管理

盡管上面介紹的基礎概念非常簡單,但是依然有一些實用的技巧可以讓管理內(nèi)存更加容易,確保你的程序在最小化內(nèi)存開銷時仍然可靠,健壯。

3.1.使用存取方法讓內(nèi)存管理更加簡單

如果你的類有一個對象類型的存取屬性,必須確保任何設置為這個值的對象在使用期間不會被銷毀。因此在設置這個對象時必須認領其所有權(quán)。同樣必須確保稍后放棄當前持有值的所有權(quán)。
有時這看起來有些冗長和迂腐的,但是如果你持續(xù)使用存取方法,那么出現(xiàn)內(nèi)存管理錯誤的概率會大大降低。如果你的代碼中的實例變量充斥著retainrelease,那么實在是在做一件錯事。
假設一個計數(shù)器對象,你想設置它的值。

@interface Counter : NSObject
@property (nonatomic, retain) NSNumber *count;
@end;

這個屬性聲明了兩個存取方法。通常的你應該請求編譯器來合成這兩個方法;但是如果了解它們是如何實現(xiàn)的是非常有意義的。
get方法,只需要返回合成的實例變量,因此不需要retainrelease

- (NSNumber *)count {
    return _count;
}

set方法,如果其它所有對象都遵循同樣的規(guī)則那么必須假設這個新值也許會在任何時候被丟棄,因此你必須持有這個對象的所有權(quán)--通過向它發(fā)送一個retain消息--來確保這個新值不會被丟棄。同樣也必須放棄舊值的所有權(quán)通過向舊值發(fā)送一個release消息(在Objective-C中向nil發(fā)送消息是允許的,如果_count還沒有被賦值的話那么實現(xiàn)仍會起作用)。必須在[newCount retain]之后發(fā)送release消息以免新值和舊值是同一個對象--你絕對不想意外的讓這個對象被銷毀。

- (void)setCount:(NSNumber *)newCount {
    [newCount retain];
    [_count release];
    // Make the new assignment.
    _count = newCount;
}

使用存取方法設置屬性值

假設你想實現(xiàn)一個方法來重置這個計數(shù)器。你有幾個選擇。第一個實現(xiàn)是使用alloc創(chuàng)建這個NSNumber實例,因此需要發(fā)送release消息來達到平衡。

- (void)reset {
    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
    [self setCount:zero];
    [zero release];
}

其次是使用一個便利構(gòu)造器來創(chuàng)建一個新的NSNumber對象。因此沒必要發(fā)送retainrelease消息

- (void)reset {
    NSNumber *zero = [NSNumber numberWithInteger:0];
    [self setCount:zero];
}

注意以上兩個方法都使用了set方法。
下面的方法對于簡單的例子大部分情況下也會正確工作,但是像繞開存取方法一樣誘人,這樣做將會很可能在某些階段導致錯誤(例如,當你忘記了retain或者release,或者對這個實例變量的內(nèi)存管理語義發(fā)生變化時)。

- (void)reset {
    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
    [_count release];
    _count = zero;
}

注意,如果你使用KVO,使用這種方式改變這個變量是不被KVO允許的。

不要在初始化和dealloc方法中使用存取方法

只有兩個地方不應該使用存取方法來設置一個實例變量:初始化方法和dealloc方法。以一個代表0的值對象初始化一個計數(shù)器對象,可以像下面這樣實現(xiàn)一個init方法:

- init {
    self = [super init];
    if (self) {
        _count = [[NSNumber alloc] initWithInteger:0];
    }
    return self;
}

為了初始化一個非0的計數(shù)器對象,可以像下面這樣實現(xiàn)一個initWithCount:方法:

- initWithCount:(NSNumber *)startingCount {
    self = [super init];
    if (self) {
        _count = [startingCount copy];
    }
    return self;
}

因為計數(shù)器類有一個對象類型的實例變量,所以必須實現(xiàn)一個dealloc方法。計數(shù)器類應該放棄任何實例變量的所有權(quán)通過發(fā)送release消息,并在最后調(diào)用父類的實現(xiàn):

- (void)dealloc {
    [_count release];
    [super dealloc];
}

3.2.使用弱引用避免循環(huán)引用

持有一個對象會產(chǎn)生對這個對象的強引用。一個對象只有當它所有的強引用被釋放時才會被銷毀。如果兩個對象有了循環(huán)引用--也就是說彼此都有一個強引用(可以是直接的,也可以是通過其它彼此從頭到尾都擁有一個強引用的對象),那么一個叫做“引用環(huán)”的問題就會產(chǎn)生。
圖1中的對象關(guān)系展示了一個潛在的引用環(huán)。Document 對象對于文檔中的每個page有一個Page對象。每個Page對象都有一個屬性記錄它屬于哪個文檔。如果Document 對象對Page對象持有一個強引用,Page 對象對Document對象持有一個強引用,那么這兩個對象永遠也不會被銷毀。Document的引用計數(shù)永遠也不會為0直到Page對象被釋放,同樣的Page對象永遠也不會被釋放直到Document對象被釋放。

循環(huán)引用圖示

解決引用環(huán)問題的方法是使用弱引用。一個弱引用是一個非持有的關(guān)系--源對象并不持有它引用的對象。
但是為了對象圖表的完整性,在某些地方必須存在強引用(如果只有弱引用,那么pages和paragraphs就不會有任何所有者,也就會被銷毀)。Cocoa建立了一套規(guī)則,“父類”對象應該對它的“子類”持有強引用,“子類”對象應該對它的“父類”持有弱引用。
因此,在圖1中,Document 對象對它的Page 對象持有強引用,Page 對象對它的Document 對象持有弱引用。
Cocoa中的弱引用例子包括:table的數(shù)據(jù)源,IBOutlet連接的視圖項,通知的觀察者,各種各樣的targets和代理等。
當向一個你持有弱引用的對象發(fā)送消息時要格外小心。如果在一個對象被銷毀后向它發(fā)送了一個消息,程序會crash。必須明確的知道這個對象何時是有效的。在大多數(shù)情況下,弱引用對象知道其它對象對它的弱引用,同循環(huán)引用一樣,當它銷毀時負責通知其它對象。例如,當你用通知中心注冊了一個對象,通知中心會存儲一個對這個對象的弱引用,當合適的通知發(fā)出后,會向這個對象發(fā)送消息。當這個對象被銷毀后,你需要在通知中心對它進行移除注冊來避免通知中心未來向這個已經(jīng)被銷毀的對象發(fā)送消息。同樣的,當一個代理對象被銷毀時,你需要通過發(fā)送setDelegate:消息傳入nil參數(shù)來移除這個代理連接。這些消息通常在dealloc方法發(fā)送。

3.3.避免引起正在使用的對象的銷毀

Cocoa的所有權(quán)策略指明接收對象在方法調(diào)用的范圍內(nèi)應該持續(xù)有效。同樣也可以從當前的范圍返回一個接收對象而無需擔心這個對象被釋放。對你的應用來說一個對象的getter方法無論是返回一個緩存的實例變量還是一個計算的值是無所謂的。重要的是這個對象在你使用它時是有效的。
針對這個策略偶爾還是有些例外的,主要在以下兩類。

  • 1.當一個對象從基礎的集合類中被移除時。
heisenObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// heisenObject could now be invalid.

當一個對象從基礎的集合類中被移除時,它會收到一條release而不是autorelease消息。如果這個集合是這個被移除對象的唯一持有者,這個被移除對象(例子中的heisenObject)會立刻被銷毀。

  • 2.當一個“父類對象”被銷毀時。
id parent = <#create a parent object#>;
// ...
heisenObject = [parent child] ;
[parent release]; // Or, for example: self.parent = nil;
// heisenObject could now be invalid.

在某些情況下你從另外一個對象獲取到一個對象,然后直接或間接地釋放了這個父類對象。如果釋放父類對象導致父類對象被銷毀,父類對象恰巧是這個子類的唯一所有者,那么子類會同時被銷毀(假設子類在父類的dealloc方法中收到了release而不是autorelease消息)。
為了避免這些情況,一旦接收到heisenObject就retain它,使用完畢后release它,例如:

heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// Use heisenObject...
[heisenObject release];

3.4.不要使用dealloc管理稀缺資源

通常的不要在dealloc方法中處理稀缺資源,例如文件描述符,網(wǎng)絡連接,緩沖區(qū)或者緩存。尤其不要設計這樣的類:當你認為dealloc方法將會被調(diào)起時它就會被調(diào)起。dealloc方法的調(diào)起也許會延遲或者干脆就不調(diào)起,這可能是因為一個bug或者應用tear-down。
相反,如果你有一個類它的實例變量管理著稀缺資源,你應該這樣設計你的應用:你知道何時不再需要這些資源并且同時通知實例來清理這些資源。然后釋放這個實例變量,緊接著dealloc會被調(diào)起,但是你不會遭到額外的問題即使dealloc未被調(diào)起。
如果你嘗試在dealloc方法中處理資源管理,那么可能導致以下問題:

  • 1.順序取決于對象圖表tear-down機制
    對象圖表tear-down機制本身是無序的。盡管你也許期望--或者得到了一個特別的順序,實際這種順序是很脆弱的。如果一個對象被不可預期的自動釋放而不是立刻釋放,tear-down順序也許就會改變,這也許會導致不可預期的結(jié)果。
  • 2.稀缺資源沒有重復利用。
    內(nèi)存泄漏是bug需要修復,但是它們通常不會立刻導致致命問題。但是如果稀有資源沒有在你期望釋放時釋放,那么你也許會陷入嚴重問題。例如,如果你的程序用光了文件描述符,用戶也許無法保存數(shù)據(jù)。
  • 3.在錯誤的線程執(zhí)行清理邏輯的操作。
    如果一個對象在一個不可預料的時間被釋放,它將會在它恰巧處在的任何線程的自動釋放池代碼塊內(nèi)被釋放。這對于應該只在一個線程訪問的資源來說很可能導致致命問題。

3.5.集合持有其包含的對象

當你向一個集合添加了一個對象,這個集合就會持有這個對象的所有權(quán)。當對象從集合移除或者集合本身被釋放時,集合會放棄對象的所有權(quán)。例如,如果你想創(chuàng)建一個數(shù)值組成的數(shù)組,可以有以下兩種方式:

NSMutableArray *array = <#Get a mutable array#>;
NSUInteger i;
// ...
for (i = 0; i < 10; i++) {
    NSNumber *convenienceNumber = [NSNumber numberWithInteger:i];
    [array addObject:convenienceNumber];
}

這個例子中,你沒有調(diào)用alloc,所以沒必要調(diào)用release。沒必要retainconvenienceNumber,因為數(shù)組會幫你做。

NSMutableArray *array = <#Get a mutable array#>;
NSUInteger i;
// ...
for (i = 0; i < 10; i++) {
    NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger:i];
    [array addObject:allocedNumber];
    [allocedNumber release];
}

這個例子中,你需要在for循環(huán)內(nèi)向allocedNumber發(fā)送release消息來平衡alloc。因為數(shù)組在調(diào)用addObject:添加值時持有了它,所以在這個值只要在數(shù)組內(nèi)就不會被銷毀。

3.6.所有權(quán)策略是通過引用計數(shù)來實現(xiàn)的

所有權(quán)策略是通過引用計數(shù)來實現(xiàn)的--通常叫做“retain count”。每個對象都有一個retain count。

  • 當你創(chuàng)建了一個對象,它的retain count為1。
  • 當你向一個對象發(fā)送一條retain消息,它的retain count加1
  • 當你向一個對象發(fā)送一條release消息,它的retain count減1
    當你向一個對象發(fā)送一條autorelease消息,它的retain count會在當前的autorelease pool block末尾減1
  • 如果一個對象的retain count減到0,它就會被銷毀
    重要:沒有理由明確的請求一個對象的retain count。結(jié)果經(jīng)常是錯誤的,因為你也許不清楚什么框架的對象持有了一個你感興趣的對象。在調(diào)試內(nèi)存管理問題時,你應該只關(guān)心確保你的代碼堅持了所有權(quán)規(guī)則。


4.使用自動釋放池代碼塊

自動釋放池代碼塊提供了一種機制:你可以放棄一個對象的所有權(quán),但是可以避免這個對象被立即銷毀(例如當你從一個方法返回一個對象時)。通常你不需要創(chuàng)建你自己的自動釋放池代碼塊,但是有些情況下你不得不創(chuàng)建或者創(chuàng)建是有利的。

4.1.關(guān)于自動釋放池代碼塊

一個自動釋放池代碼塊以@autoreleasepool標記,像下面這樣:

@autoreleasepool {
    // Code that creates autoreleased objects.
}

在自動釋放池代碼塊末尾,在塊內(nèi)接收了autorelease消息的對象會接收一條release消息--對象每在塊內(nèi)接收一次autorelease消息,就會在塊末尾收到一條release消息。
像其它代碼塊一樣,自動釋放池代碼塊也可以嵌套:

@autoreleasepool {
    // . . .
    @autoreleasepool {
        // . . .
    }
    . . .
}

對于一條指定的autorelease消息,對應的release消息會在autorelease消息被發(fā)送的自動釋放池代碼塊末尾發(fā)送。
Cocoa總是期望代碼在自動釋放池代碼塊內(nèi)執(zhí)行,否則自動釋放的對象得不到釋放你的應用就會泄漏內(nèi)存(如果在自動釋放池代碼塊外發(fā)送autorelease消息,Cocoa會報錯)。AppKit和UIKit框架在自動釋放池代碼塊內(nèi)處理每個時間循環(huán)迭代(例如鼠標下移或者點擊)。因此你通常不需要自己創(chuàng)建一個自動釋放池代碼塊,或者甚至看不到這樣的代碼。但是,有三種情況你可能需要使用你自己的自動釋放池代碼塊:

  • 如果你在編寫一個不是基于UI框架的項目,例如命令行工具
  • 如果你編寫了一個產(chǎn)生許多臨時對象的循環(huán)
    你也許可以在下一個迭代之前在這個循環(huán)內(nèi)部使用一個自動釋放池代碼塊來去除那些臨時對象。在循環(huán)內(nèi)使用一個自動釋放池代碼塊可以幫助降低程序的內(nèi)存峰值。
  • 如果你大量創(chuàng)建了子線程。
    你必須在線程開始執(zhí)行前創(chuàng)建你自己的自動釋放池代碼塊;否則你的應用將會泄漏內(nèi)存。

4.2.使用局部的自動釋放池代碼塊降低內(nèi)存峰值

許多程序會創(chuàng)建自動釋放的臨時對象。這些對象會添加到程序的內(nèi)存中直到block的結(jié)尾。在許多情況下,允許臨時對象在當前時間循環(huán)迭代結(jié)束前累計不會導致過多的開銷;但是在一些情況下,你也許會創(chuàng)建大量的臨時對象持續(xù)的添加到內(nèi)存中,你想更快速的處理掉它們。這時,你可以創(chuàng)建你自己的自動釋放池代碼塊。在塊的末尾,這些臨時對象會被銷毀,從而降低程序的內(nèi)存。
下面的例子展示了如何在for循環(huán)內(nèi)使用局部的自動釋放池代碼塊。

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)每次處理一個文件。任何在塊內(nèi)接收autorelease消息的對象(例如fileContents)都會在塊的末尾被釋放。
在一個自動釋放池代碼塊之后,你應該將任何在塊內(nèi)自動釋放的對象當做被處理掉了。不要向這個對象發(fā)送消息或者將這個對象返回給方法的調(diào)用者。如果你必須要在塊外使用一個臨時對象,那么應該在塊內(nèi)向這個對象發(fā)送retain消息,塊外發(fā)送autorelease消息,像下面這樣:

– (id)findMatchingObject:(id)anObject {
 
    id match;
    while (match == nil) {
        @autoreleasepool {
 
            /* Do a search that creates a lot of temporary objects. */
            match = [self expensiveSearchForObject:anObject];
 
            if (match != nil) {
                [match retain]; /* Keep match around. */
            }
        }
    }
 
    return [match autorelease];   /* Let match go and return it. */
}

在塊內(nèi)向match發(fā)送retain消息并且在塊外發(fā)送autorelease消息擴展了match的聲明周期,允許它在循環(huán)外部接收消息并返回給findMatchingObject:的調(diào)用者。

4.3.自動釋放池代碼塊和線程

Cocoa應用的每個線程都維持著它自己的自動釋放池代碼塊棧。如果你編寫的是純Foundation的程序或者detach了一個線程,你需要創(chuàng)建自己的自動釋放池代碼塊。
如果你的應用或者線程是長期存在的,并且可能產(chǎn)生大量的自動釋放對象,你應該使用自動釋放池代碼塊。否則,自動釋放的對象會累積,你的內(nèi)存會增長。如果你detached的線程沒有做Cocoa調(diào)用,那么沒必要使用自動釋放池代碼塊。
注意:如果你使用POSIX線程API而不是NSThread開辟了子線程,你不能使用Cocoa除非Cocoa在多線程模式。Cocoa 只在分離出它的第一個NSThread對象時進入多線程模式。為了在POSIX的子線程使用Cocoa,你的程序必須首先分離出至少一個可以立刻退出的NSThread對象??梢允褂?code>NSThread類的isMultiThreaded方法測試是否在多線程模式。

5.參考文獻



提升代碼質(zhì)量最神圣的三部曲:模塊設計(謀定而后動) -->無錯編碼(知止而有得) -->開發(fā)自測(防患于未然)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容