初學 iOS 時正值 ARC 全面普及,記憶體管理似乎已成歷史。然而接手的專案為舊代碼庫,強制使用 MRC(Manual Retain-Release)。初期對 retain/release/autorelease 頭緒混亂,但隨著深入鑽研,反而對物件生命週期、引用計數、所有權轉移有了更本質的認知。本文將從物件圖結構、臨時與持久物件區分、屬性修飾詞、自動釋放池、特殊場景五個維度,系統梳理 MRC 精髓。
將整個 App 視為一棵以 UIApplication 為根的物件樹:
retain/strong)assign/weak)dealloc 中)檢查策略:從根節點出發,逐層驗證:
pushViewController 後 pop,該 VC 是否釋放?_subView、_dataArray)是否在 dealloc 釋放?這種「自頂向下、層層驗證」的思維模型,比零散記憶口訣可靠得多。
鐵律:alloc/new/copy/mutableCopy/retain → 擁有權+1 → 你必須 release/autorelease 歸還。
反之,非你持有的物件(如工廠方法回傳、arrayWithObjects:),嚴禁 release。
UIView* subView = [[UIView alloc] initWithFrame:rect]; [self.view addSubview:subView]; // self.view retain 了 subView [subView release]; // 釋放「我」的擁有權,subView 現在由 self.view 擁有 // 當 subView 被移除,self.view release,計數歸零,記憶體釋放
關鍵點:臨時變數離開作用域指標即失效,必須在方法結束前 release,否則洩漏。
// 正確:成員變數持有
_subView = [[UIView alloc] initWithFrame:rect];
[self.view addSubview:_subView]; // 現在有兩個擁有者:self 和 self.view
// 錯誤:在此 release _subView 會導致懸空指標
// [_subView release]; // ❌
// 正確:在 dealloc 中釋放
- (void)dealloc {
[_subView release];
[super dealloc];
}
成員變數「不釋放」是為了確保宿主存活期間物件常駐記憶體。只有宿主 dealloc 時統一釋放。
// @property (retain) 生成的 setter
- (void)setXXX:(UIView*)XXX {
[XXX retain]; // 新值持有
[_XXX release]; // 舊值釋放
_XXX = XXX; // 賦值
}
// @property (assign) 生成的 setter
- (void)setXXX:(UIView*)XXX {
_XXX = XXX; // 直接賦值,無 retain/release
}
retain:獲取擁有權,保證物件在 setter 執行期間不被釋放。適用於需要長期持有的物件(UIView、Model、Array 等)。
assign:不獲取擁有權。適用於:
血淚教訓:
UIView* view1 = [[UIView alloc] init]; _XXX = view1; // 直接賦值,未 retain [view1 release]; // view1 釋放,_XXX 成為野指標 // 後續使用 _XXX → EXC_BAD_ACCESS
為何需要? 工廠方法回傳新建物件,呼叫端未持有,若內部 release 物件即死;若不 release 則洩漏。
- (NSString*)createString {
NSString* str = [[NSString alloc] init]; // retainCount = 1
return [str autorelease]; // 標記:稍後由 autorelease pool 釋放
}
自動釋放池:RunLoop 每次迴圈建立/銷毀池。autorelease 物件在池排空時收到 release。適用於「建立即回傳、不持有」的場景。
陷阱:大量臨時物件在循環中 autorelease 會導致記憶體尖峰。解法:在循環內建立區域性 @autoreleasepool { }。
| API | 行為 | 對策 |
|---|---|---|
addSubview: | retain 子視圖 | 加入後 release 臨時變數;移除時父視圖 release |
NSMutableArray addObject: | retain 元素 | 同前 |
Block | 捕獲區域變數 → retain(__strong) | 使用 __weak/__block 打破循環 |
NSTimer | retain target | 必須 invalidate 才能釋放 target |
字串常量 @"text" | 靜態區,不需管理 | 忽略 |
NSZombieEnabled=YES 捕獲訪問已釋放物件。Product → Analyze 可發現大多數 MRC 錯誤。_ 開頭;工廠方法回傳 autorelease 物件。MRC 雖繁瑣,卻強迫開發者建立清晰的「所有權模型」:誰建立、誰持有、誰負責釋放。這種思維在 ARC 時代同樣適用——ARC 只是編譯器幫你插入 retain/release,理解底層機制才能寫出高效、無洩漏的程式碼。若專案允許,務必遷移至 ARC;若必須維護 MRC,請建立團隊規範、配合靜態分析、嚴格 Code Review。