MRC 時代的內存管理

簡介

應用程序內存管理是在程序運行時分配內存,使用它並在完成後釋放內存的過程。編寫良好的程序使用盡量少的內存。在 Objective-C 中,它還能夠被看做是在許多數據和代碼之間分配有限內存資源的全部權的一種方式。內存管理的核心思想是:明確管理對象的生命週期,並在再也不須要時釋放它們。數組

雖然內存管理一般被視爲單個對象的級別,但你的目標其實是管理對象圖。你但願確保內存中沒有比實際須要的對象更多的對象。緩存

Objective-C 提供了兩種應用程序內存管理方法。安全

  1. 「manual retain-release」 或 MRR,你經過跟蹤你擁有的對象來明確管理內存。這是使用一個稱爲引用計數的模型實現的,Foundation 框架中的 NSObject 類與運行時環境一塊兒提供。網絡

  2. 在自動引用計數或 ARC 中,系統使用與 MRR 相同的引用計數系統,但它在編譯時爲你插入適當的內存管理方法調用。若是你使用 ARC,一般不須要理解底層實現,儘管在某些狀況下它可能會有所幫助。框架

良好實踐可防止與內存相關的問題

這裏有兩種主要致使內存管理不正確的問題:工具

  • 釋放或覆蓋仍在使用的數據性能

    這會致使內存損壞,而且一般會致使應用程序崩潰,甚至致使用戶數據損壞。ui

  • 不釋放再也不使用的數據會致使內存泄漏atom

    內存泄漏是指未釋放已分配內存,即便它從未再次使用過。泄漏會致使應用程序不斷增長的內存使用量,從而致使系統性能降低或應用程序被終止。url

可是,從引用計數的角度考慮內存管理一般會拔苗助長,由於你傾向於根據實現細節而不是實際目標來考慮內存管理。相反,你應該從對象全部權和對象圖的角度考慮內存管理。

Cocoa 使用簡單的命名約定來指示你擁有方法返回的對象的時間。

雖然基本策略很簡單,但你能夠採起一些實際步驟來簡化內存管理,並幫助確保你的程序保持可靠和健壯,同時最大限度地減小其資源需求。

自動釋放池提供了一種機制,你能夠經過該機制向對象發送「延遲(deferred)」釋放消息。這在你想要放棄對象的全部權但但願避免當即釋放它的可能性(例如從方法返回對象時)的狀況下很是有用。有時你可能會使用本身的自動釋放池。

使用分析工具調試內存問題

要在編譯時識別代碼問題,可使用 Xcode 中內置的 Clang Static Analyzer。

若是仍然出現內存管理問題,你可使用其餘工具和技術來識別和診斷問題。

  • 許多工具和技術都在 Technical Note TN2239,iOS Debugging Magic 中進行了描述,特別是使用 NSZombie 來幫助找到過分釋放的對象。

  • 你可使用 Instruments 跟蹤引用計數事件並查找內存泄漏。

內存管理策略

在引用計數環境中用於內存管理的基本模型由 NSObject 協議中定義的方法和標準方法命名約定的組合提供。NSObject 類還定義了一個方法 dealloc,該方法在對象銷燬時自動調用。

基本內存管理規則

內存管理模型基於對象全部權。任何對象均可能擁有一個或多個全部者。只要一個對象至少有一個全部者,它就會繼續存在。若是對象沒有全部者,則運行時系統會自動銷燬它。爲了確保什麼時候你擁有對象什麼時候你不擁有對象的時機是清晰的,Cocoa 設置如下策略:

  • 你擁有本身建立的任何對象

    使用名稱以「alloc」,「new」,「copy」或「mutableCopy」開頭的方法(例如,alloc,newObject 或 mutableCopy)建立對象。

  • 你可使用 retain 獲取對象的全部權

    一般保證接收到的對象在接收到的方法中保持有效,而且該方法也能夠安全地將對象返回給其調用者。在兩種狀況下使用 retain:(1)在 accessor 方法或 init 方法的實現中,獲取要存儲爲對象屬性的對象的全部權;(2)防止對象因某些其餘操做的反作用而失效。

  • 當你再也不須要它時,你必須放棄你擁有的對象的全部權

    你經過向對象發送 release 消息或 autorelease 消息來放棄對象的全部權。所以,在 Cocoa 術語中,放棄對象的全部權一般被稱爲「釋放(releasing)」對象。

  • 你不得放棄你不擁有的對象的全部權

    這只是先前明確規定的策略規則的必然結果。

一個簡單的例子

要說明策略,請考慮如下代碼片斷:

{
    Person *aPerson = [[Person alloc] init];
    // ...
    NSString *name = aPerson.fullName;
    // ...
    [aPerson release];
}
複製代碼

Person 對象是使用 alloc 方法建立的,所以在再也不須要時會發送一條釋放消息。不使用任何擁有方法檢索此人的姓名,所以不會發送 release 消息。但請注意,該示例使用的是 release 而不是 autorelease。

使用 autorelease 發送延遲 release

當你須要發送延遲 release 消息時,一般在從方法返回對象時使用 autorelease。例如,你能夠像這樣實現 fullName 方法:

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

你擁有 alloc 返回的字符串。要遵照內存管理規則,你必須在丟失對該字符串的引用以前放棄該字符串的全部權。可是,若是使用 release,則在返回以前將釋放該字符串(而且該方法將返回無效對象)。使用 autorelease,表示你要放棄全部權,但容許方法的調用者在釋放以前使用返回的字符串。

你還能夠像這樣實現 fullName 方法:

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

遵循基本規則,你不擁有 stringWithFormat: 返回的字符串,所以你能夠安全地從方法返回字符串。

相比之下,如下實現是錯誤的:

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

根據命名約定,沒有任何東西能夠表示 fullName 方法的調用者擁有返回的字符串。所以調用者沒有理由釋放返回的字符串,所以它將被泄露。

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

Cocoa 中的一些方法指定經過引用返回一個對象(即,它們採用 ClassName ** 或 id * 類型的參數)。常見的模式是使用 NSError 對象,該對象包含有關錯誤的信息(若是發生),如 initWithContentsOfURL:options:error: (NSData) 和 initWithContentsOfFile:encoding:error: (NSString) 所示。

在這些狀況下,適用的規則與已經描述的相同。當你調用這些方法中的任何一個時,你不會建立 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];
複製代碼

實現 dealloc 以放棄對象的全部權

NSObject 類定義了一個方法 dealloc,該方法在對象沒有全部者而且其內存被回收時自動調用 - 在 Cocoa 術語中它被「釋放(freed)」或「解除分配(deallocated)」。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
複製代碼

重要說明:永遠不要直接調用另外一個對象的 dealloc 方法。你必須在實現結束時調用超類的實現。你不該該將系統資源的管理與對象生命週期聯繫起來。當應用程序終止時,可能不會向對象發送 dealloc 消息。由於進程的內存在退出時自動清除,因此僅僅容許操做系統清理資源比調用全部內存管理方法更有效。

Core Foundation 使用類似但不一樣的規則

Core Foundation 對象有相似的內存管理規則。可是,Cocoa 和 Core Foundation 的命名約定是不一樣的。特別是,Core Foundation 的 Create Rule 不適用於返回 Objective-C 對象的方法。例如,在如下代碼片斷中,你不負責放棄 myInstance 的全部權:

MyClass *myInstance = [MyClass createInstance];
複製代碼

實用的內存管理

雖然內存管理策略中描述的基本概念很簡單,但你能夠採起一些實際步驟來簡化內存管理,並幫助確保你的程序保持可靠和健壯,同時最大限度地減小其資源需求。

使用訪問器方法使內存管理更容易

若是你的類具備做爲對象的屬性,則必須確保在你使用它時不會釋聽任何設置爲該值的對象。所以,你必須在設置對象時聲明對象的全部權。你還必須確保放棄任何當前持有的值的全部權。

有時它可能看起來很乏味或迂腐,但若是你一直使用訪問器方法,那麼內存管理問題的可能性會大大下降。若是你在整個代碼中使用實例變量的 retain 和 release,那麼你幾乎確定會作錯事。

考慮一個你想要設置其計數的 Counter 對象。

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

該屬性聲明瞭兩種訪問方法。一般,你應該要求編譯器合成方法; 可是,瞭解它們如何實現是有益的。

在「get」訪問器中,你只需返回合成的實例變量,所以不須要 retain 或 release:

- (NSNumber *)count {
    return _count;
}
複製代碼

在「set」方法中,若是其餘全部人都按照相同的規則進行遊戲,則必須假設新計數能夠隨時處理,所以你必須取得對象的全部權 - 經過向其發送 retain 消息 - 以確保它不會被釋放。你還必須經過向其發送 release 消息來放棄舊計數對象的全部權。(在 Objective-C 中容許向 nil 發送消息,所以若是還沒有設置 _count,則實現仍然有效。)你必須在[newCount retain]以後發送此消息,以防二者是同一個對象 - 你不想無心中致使它被釋放。

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

使用訪問器方法設置屬性值

假設你要實現重置計數器的方法。你有幾個選擇。第一個實現使用 alloc 建立 NSNumber 實例,所以你能夠經過 release 將其進行平衡。

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

第二個使用便捷構造方法來建立新的 NSNumber 對象。所以,不須要 retain 或 release 消息

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

請注意,二者都使用 set 訪問器方法。

對於簡單的狀況,如下幾乎確定會正常工做,可是儘量避免使用訪問器方法,這樣作幾乎確定會在某個階段致使錯誤(例如,當你忘記 retain 或 release 時,或者若是實例變量的內存管理語義更改)。

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

另請注意,若是使用 key-value observing,則以這種方式更改變量不會觸發 KVO。

不要在初始化方法和 dealloc 中使用訪問器方法

你不該該使用訪問器方法來設置實例變量的惟一地方是初始化方法和 dealloc。要使用表示零的數字對象初始化計數器對象,能夠按以下方式實現 init 方法:

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

要容許使用非零計數初始化計數器,你能夠實現 initWithCount: 方法,以下所示:

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

因爲 Counter 類具備對象實例變量,所以還必須實現 dealloc 方法。它應該經過向它們發送 release 消息來放棄任何實例變量的全部權,並最終應該調用 super 的實現:

- (void)dealloc {
    [_count release];
    [super dealloc];
}
複製代碼

使用弱引用來避免循環 Retain

Retaining 對象會建立對該對象的強引用。在 released 全部強引用以前,不能釋放對象。所以,若是兩個對象可能具備循環引用,則會出現一個被稱爲循環 retain 的問題 - 也就是說,它們彼此之間具備強引用(直接或經過一系列其餘對象,每一個對象都強引用下一個對象直到回到第一個)。

圖1中所示的對象關係說明了潛在的循環 retain。Document 對象具備文檔中每一個頁面的 Page 對象。每一個 Page 對象都有一個屬性,用於跟蹤它所在的文檔。若是 Document 對象具備對 Page 對象的強引用,而且 Page 對象具備對 Document 對象的強引用,則任何對象都不能被釋放。在釋放 Page 對象以前,Document 的引用計數不能爲零,而且在取消分配 Document 對象以前不會釋放 Page 對象。

圖 1 循環引用的圖示

循環 retain 問題的解決方案是使用弱引用。弱引用是非擁有關係,其中源對象不保留它具備引用的對象。

可是,爲了保持對象圖無缺無損,必須在某處有強引用(若是隻有弱引用,則頁面和段落可能沒有任何全部者,所以將被釋放)。所以,Cocoa 創建了一個約定,即父對象應該對其孩子保持強引用,而且孩子們應該有對父對象的弱引用。

所以,在圖1中,文檔對象具備對(retains)其頁面對象的強引用,但頁面對象具備對(not retain)文檔對象的弱引用。

Cocoa 中弱引用的示例包括但不限於 table data sources,outline view items,notification observers 以及其餘 targets 和 delegates。

你須要注意將消息發送到僅包含弱引用的對象。若是在銷燬對象後向對象發送消息,則應用程序將崩潰。對象有效時,你必須具備明肯定義的條件。在大多數狀況下,弱引用對象知道另外一個對象對它的弱引用,就像循環引用的狀況同樣,而且負責在銷燬時時通知另外一個對象。例如,當你向通知中心註冊對象時,通知中心會存儲對該對象的弱引用,並在發佈相應的通知時向其發送消息。銷燬對象後,你須要將其註銷到通知中心,以防止通知中心向該對象發送任何再也不存在的消息。一樣,當銷燬 delegate 對象時,你須要經過向另外一個對象發送帶有 nil 參數的 setDelegate: 消息來刪除 delegate 引用。這些消息一般從對象的 dealloc 方法發送。

避免致使你正在使用的對象被銷燬

Cocoa 的全部權策略指定接收的對象一般應該在調用方法的整個範圍內保持有效。還應該能夠從當前範圍返回接收到的對象而不用擔憂它被釋放。對於你的應用程序而言,對象的 getter 方法返回緩存的實例變量或計算值應該可有可無。重要的是該對象在你須要的時間內仍然有效。

此規則偶爾會有例外狀況,主要分爲兩類。

  1. 從一個基本集合類中刪除對象時。
heisenObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// heisenObject could now be invalid.
複製代碼

從其中一個基本集合類中刪除對象時,會向其發送一個 release(而不是 autorelease)消息。若是集合是已刪除對象的惟一全部者,則會當即釋放已刪除的對象(示例中爲 heisenObject)。

  1. 當「父對象」被釋放時。
id parent = <#create a parent object#>;
// ...
heisenObject = [parent child] ;
[parent release]; // Or, for example: self.parent = nil;
// heisenObject could now be invalid.
複製代碼

在某些狀況下,你從另外一個對象檢索對象,而後直接或間接釋放父對象。若是釋放父對象致使它被釋放,而且父對象是子對象的惟一全部者,則子對象(示例中的 heisenObject)將同時被釋放(假設它被髮送一個 release 而不是一個 autorelease 消息在父對象的 dealloc 方法中)。

爲了防止這些狀況,你在收到 heisenObject 後會保留它,並在完成後將其釋放。例如:

heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// Use heisenObject...
[heisenObject release];
複製代碼

不要使用 dealloc 來管理稀缺資源

你一般不該該在 dealloc 方法中管理稀缺資源,例如文件描述符,網絡鏈接以及緩衝區或緩存。特別是,你不該該設計類,以便在你認爲將調用 dealloc 時調用 dealloc。因爲錯誤或應用程序拆除(tear-down),dealloc 的調用可能會被延遲或迴避。

相反,若是你有一個實例管理稀缺資源的類,你應該設計你的應用程序,以便你知道什麼時候再也不須要資源,而後能夠告訴實例在那時「清理」。你一般會釋放該實例,dealloc 會跟隨,但若是沒有,你將不會遇到其餘問題。

若是你嘗試在 dealloc 之上捎帶資源管理,則可能會出現問題。例如:

  1. 對象圖拆除的順序依賴性。

    對象圖拆除機制本質上是無序的。雖然你一般會指望並得到特定順序,但你會引入脆弱性。若是對象意外地自動釋放而不是正常釋放,則拆卸順序可能會改變,這可能會致使意外結果。

  2. 不回收稀缺資源。

    內存泄漏是應該修復的錯誤,但它們一般不會當即致命。可是,若是在你但願釋放資源時沒有釋放稀缺資源,則可能會遇到更嚴重的問題。例如,若是你的應用程序用完了文件描述符,則用戶可能沒法保存數據。

  3. 在錯誤的線程上執行清理邏輯。

    若是一個對象在乎外的時間自動釋放,它將在它碰巧進入的任何線程的自動釋放池塊中被釋放。對於只能從一個線程觸及的資源來講,這很容易致命。

集合擁有它們包含的對象

將對象添加到集合(例如數組,字典或集合)時,集合將得到對象的全部權。當從集合中移除對象或集合自己被釋放時,集合將放棄全部權。所以,例如,若是要建立數字數組,能夠執行如下任一操做:

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

在這種狀況下,你沒有調用 alloc,所以無需調用 release。不須要保留新數字(convenienceNumber),由於數組會這樣作。

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 循環的範圍內發送 allocedNumber 釋放消息以平衡 alloc。因爲數組在 addObject: 添加時 retained 了數字,所以在數組中它不會被釋放。

要理解這一點,請將本身置於實現集合類的人的位置。你但願確保沒有給予你照顧的對象從你的下方消失,所以你在傳入時向他們發送 retain 消息。若是它們被刪除,你必須發送 release 消息以保持平衡,在你本身的 dealloc 方法中,應該向任何剩餘的對象發送一條 release 消息。

使用 Retain Counts 實現全部權政策

全部權策略經過引用計數實現 - 一般在 retain 方法以後稱爲「retain count」。每一個對象都有一個 retain count。

  • 建立對象時,其 retain count 爲1。

  • 向對象發送 retain 消息時,其 retain count 將增長1。

  • 向對象發送 release 消息時,其 retain count 減1。

    當你向對象發送 autorelease 消息時,其 retain count 在當前自動釋放池的末尾遞減1。

  • 若是對象的 retain count 減小到零,則將其銷燬。

重要提示:應該沒有理由明確詢問對象的 retain count 是什麼。結果一般會產生誤導,由於你可能不知道哪些框架對象 retained 了你感興趣的對象。在調試內存管理問題時,你應該只關心確保代碼遵照全部權規則。

使用 Autorelease Pool Blocks

自動釋放池塊提供了一種機制,你能夠放棄對象的全部權,但避免當即釋放它(例如從方法返回對象時)。一般,你不須要建立本身的自動釋放池塊,但在某些狀況下,你必須或者這樣作是有益的。

關於 Autorelease Pool Blocks

使用 @autoreleasepool 標記 autorelease pool block,如如下示例所示:

@autoreleasepool {
    // Code that creates autoreleased objects.
}
複製代碼

在 autorelease pool block 的末尾,在塊中接收到 autorelease 消息的對象被髮送 release 消息 - 對象在每次在塊內發送 autorelease 消息時接收 release 消息。

與任何其餘代碼塊同樣,autorelease pool blocks 能夠嵌套:

@autoreleasepool {
    // . . .
    @autoreleasepool {
        // . . .
    }
    . . .
}
複製代碼

(你一般不會徹底按照上面的方式看到代碼;一般,一個源文件中的 autorelease pool block 中的代碼將調用另外一個源文件中的包含在 autorelease pool block 中的代碼。)對於給定的 autorelease 消息,相應的 release 消息在 autorelease pool block 的末尾向發送過 autorelease 消息的對象發送。

Cocoa 老是但願代碼在 autorelease pool block 中執行,不然自動釋放的對象不會被釋放而應用程序會泄漏內存。(若是你在 autorelease pool block 以外發送 autorelease 消息,Cocoa 會記錄一個合適的錯誤消息。)AppKit 和 UIKit 框架處理 autorelease pool block 中的每一個事件循環迭代(例如鼠標按下事件或敲擊)。所以,你一般沒必要本身建立 autorelease pool block,甚至沒必要查看用於建立池的代碼。可是,有三種狀況可能會使用你本身的自動釋放池塊:

  • 若是你正在編寫不基於 UI 框架的程序,例如命令行工具。

  • 若是編寫一個建立許多臨時對象的循環。

    你能夠在循環內使用 autorelease pool block 在下一次迭代以前處理這些對象。在循環中使用 autorelease pool block 有助於減小應用程序的最大內存佔用量。

  • 若是你產生一個輔助線程。

    一旦線程開始執行,你必須建立本身的 autorelease pool block; 不然,你的應用程序將泄漏對象。

使用 Local Autorelease Pool Blocks 來減小峯值內存佔用量

許多程序建立自動釋放的臨時對象。這些對象會添加到程序的內存佔用空間,直到塊結束。在許多狀況下,容許臨時對象累積直到當前事件循環迭代結束時不會致使過多的開銷; 可是,在某些狀況下,你可能會建立大量臨時對象,這些對象會大大增長內存佔用,而且你但願更快地處置。在後面這些狀況下,你能夠建立本身的 autorelease pool block。在塊結束時,臨時對象被釋放,這一般致使它們的釋放,從而減小程序的內存佔用。

如下示例顯示瞭如何在 for 循環中使用 local autorelease pool block。

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 循環一次處理一個文件。在 autorelease pool block 內發送 autorelease 消息的任何對象(例如 fileContents)在塊結束時釋放。

在 autorelease pool block 以後,你應該將塊中自動釋放的任何對象視爲「已處置」。不要向該對象發送消息或將其返回給你的方法的調用者。若是必須使用 autorelease pool block 以外的臨時對象,則能夠經過向塊內的對象發送保留消息,而後在塊以後將其自動釋放發送,如此示例所示:

– (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. */
}
複製代碼

發送 retain 以在自 autorelease pool block 中匹配並在 autorelease pool block 延長匹配的生命週期後向其發送自動釋放,並容許它在循環外接收消息並返回到 findMatchingObject: 的調用者。

Autorelease Pool Blocks 和線程

Cocoa 應用程序中的每一個線程都維護本身的 autorelease pool blocks 棧。若是你正在編寫僅基於 Foundation 的程序或者分離線程,則須要建立本身的 autorelease pool block。

若是你的應用程序或線程長壽而且可能生成大量自動釋放的對象,則應使用 autorelease pool blocks(如主線程上的 AppKit 和 UIKit); 不然,自動釋放的對象會累積,而且你的內存佔用會增長。若是你的分離線程沒有進行 Cocoa 調用,則不須要使用 autorelease pool block。

相關文章
相關標籤/搜索