首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

我對 Objective-C 手動記憶體管理的深度理解與實戰總結

     編輯:關於C語言

前言:從 ARC 回歸 MRC 的旅程

初學 iOS 時正值 ARC 全面普及,記憶體管理似乎已成歷史。然而接手的專案為舊代碼庫,強制使用 MRC(Manual Retain-Release)。初期對 retain/release/autorelease 頭緒混亂,但隨著深入鑽研,反而對物件生命週期、引用計數、所有權轉移有了更本質的認知。本文將從物件圖結構、臨時與持久物件區分、屬性修飾詞、自動釋放池、特殊場景五個維度,系統梳理 MRC 精髓。

一、宏觀視角:物件圖與所有權樹

將整個 App 視為一棵以 UIApplication 為根的物件樹:

  • 父物件持有子物件(retain/strong)
  • 子物件不應持有父物件(避免循環引用,需用 assign/weak)
  • 物件釋放時,必須釋放其持有的所有子物件(在 dealloc 中)

檢查策略:從根節點出發,逐層驗證:

  1. pushViewController 後 pop,該 VC 是否釋放?
  2. VC 的成員變數(_subView、_dataArray)是否在 dealloc 釋放?
  3. VC 方法中建立的臨時物件是否在方法結束前釋放?
  4. 自訂 Cell、Tool 類別內部持有的物件是否正確釋放?

這種「自頂向下、層層驗證」的思維模型,比零散記憶口訣可靠得多。

二、核心原則:你 retain 的你負責 release

鐵律: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 時統一釋放。

三、屬性修飾詞:retain vs assign 的本質差異

// @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:不獲取擁有權。適用於:

  • 基本資料型別(int、float、BOOL)
  • delegate / 弱引用場景(避免循環引用)
  • 已知物件生命週期超過持有者(極少見)

血淚教訓:

UIView* view1 = [[UIView alloc] init];
_XXX = view1; // 直接賦值,未 retain
[view1 release]; // view1 釋放,_XXX 成為野指標
// 後續使用 _XXX → EXC_BAD_ACCESS

四、autorelease:延遲釋放的藝術

為何需要? 工廠方法回傳新建物件,呼叫端未持有,若內部 release 物件即死;若不 release 則洩漏。

- (NSString*)createString {
    NSString* str = [[NSString alloc] init]; // retainCount = 1
    return [str autorelease]; // 標記:稍後由 autorelease pool 釋放
}

自動釋放池:RunLoop 每次迴圈建立/銷毀池。autorelease 物件在池排空時收到 release。適用於「建立即回傳、不持有」的場景。

陷阱:大量臨時物件在循環中 autorelease 會導致記憶體尖峰。解法:在循環內建立區域性 @autoreleasepool { }。

五、特殊場景:系統 API 的隱性 retain

API行為對策
addSubview:retain 子視圖加入後 release 臨時變數;移除時父視圖 release
NSMutableArray addObject:retain 元素同前
Block捕獲區域變數 → retain(__strong)使用 __weak/__block 打破循環
NSTimerretain target必須 invalidate 才能釋放 target
字串常量 @"text"靜態區,不需管理忽略

六、除錯技巧與工具

  • Zombie Objects:NSZombieEnabled=YES 捕獲訪問已釋放物件。
  • Instruments → Leaks/Allocations:視覺化洩漏與保留循環。
  • 靜態分析:Xcode Product → Analyze 可發現大多數 MRC 錯誤。
  • 命名規範:凡持有成員變數,命名以 _ 開頭;工廠方法回傳 autorelease 物件。

總結

MRC 雖繁瑣,卻強迫開發者建立清晰的「所有權模型」:誰建立、誰持有、誰負責釋放。這種思維在 ARC 時代同樣適用——ARC 只是編譯器幫你插入 retain/release,理解底層機制才能寫出高效、無洩漏的程式碼。若專案允許,務必遷移至 ARC;若必須維護 MRC,請建立團隊規範、配合靜態分析、嚴格 Code Review。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved