MRC 時代的記憶體管理

小橘爺發表於2019-04-28

簡介

應用程式記憶體管理是在程式執行時分配記憶體,使用它並在完成後釋放記憶體的過程。編寫良好的程式使用盡可能少的記憶體。在 Objective-C 中,它還可以被看作是在許多資料和程式碼之間分配有限記憶體資源的所有權的一種方式。記憶體管理的核心思想是:明確管理物件的生命週期,並在不再需要時釋放它們。

雖然記憶體管理通常被視為單個物件的級別,但你的目標實際上是管理物件圖。你希望確保記憶體中沒有比實際需要的物件更多的物件。

MRC 時代的記憶體管理

Objective-C 提供了兩種應用程式記憶體管理方法。

  1. “manual retain-release” 或 MRR,你通過跟蹤你擁有的物件來明確管理記憶體。這是使用一個稱為引用計數的模型實現的,Foundation 框架中的 NSObject 類與執行時環境一起提供。

  2. 在自動引用計數或 ARC 中,系統使用與 MRR 相同的引用計數系統,但它在編譯時為你插入適當的記憶體管理方法呼叫。如果你使用 ARC,通常不需要理解底層實現,儘管在某些情況下它可能會有所幫助。

良好實踐可防止與記憶體相關的問題

這裡有兩種主要導致記憶體管理不正確的問題:

  • 釋放或覆蓋仍在使用的資料

    這會導致記憶體損壞,並且通常會導致應用程式崩潰,甚至導致使用者資料損壞。

  • 不釋放不再使用的資料會導致記憶體洩漏

    記憶體洩漏是指未釋放已分配記憶體,即使它從未再次使用過。洩漏會導致應用程式不斷增加的記憶體使用量,從而導致系統效能下降或應用程式被終止。

但是,從引用計數的角度考慮記憶體管理通常會適得其反,因為你傾向於根據實現細節而不是實際目標來考慮記憶體管理。相反,你應該從物件所有權和物件圖的角度考慮記憶體管理。

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 迴圈引用的圖示

MRC 時代的記憶體管理

迴圈 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。

相關文章