Objective-C 採用手動引用計數 (MRC, Manual Retain-Release) 機制:每個物件內部維護 retainCount (保留計數)。
alloc/new/copy/mutableCopy → 建立物件,retainCount = 1,呼叫者擁有物件。retain → retainCount++,呼叫者獲得所有權。release → retainCount--,呼叫者放棄所有權;歸零時自動呼叫 dealloc。autorelease → 註冊到當前 NSAutoreleasePool,稍後 (通常在 RunLoop 迴圈結束) 自動 release。鐵律:絕不直接呼叫 dealloc (除非在自訂 dealloc 中呼叫 [super dealloc])。
| 方法名稱前綴 | 回傳物件所有權 | 呼叫者責任 |
|---|---|---|
alloc, new, copy, mutableCopy | 擁有 (retainCount=1) | 必須 release 或 autorelease |
其他 (如 stringWithFormat:, arrayWithObject:) | 不擁有 (已 autorelease) | 若需持久持有,呼叫 retain;否則無需釋放 |
口訣:「你創建/保留/複製 → 你負責釋放;你只是借用 → 不要釋放。」
// 正確:建立後立即使用,用完釋放
Person *p = [[Person alloc] initWithName:@"張三"];
NSLog(@"%@", p.name);
[p release]; // 配對 alloc
// 錯誤:未釋放 → 記憶體洩漏
Person *p2 = [[Person alloc] initWithName:@"李四"];
// 直接跳過 release
// 錯誤:雙重釋放 → 崩潰 (double free)
[p release];
[p release];
// 類別方法實作
+ (instancetype)personWithName:(NSString *)name {
Person *p = [[Person alloc] initWithName:name];
return [p autorelease]; // 關鍵:放入自動釋放池,延遲釋放
}
// 呼叫端:無需 release
Person *p = [Person personWithName:@"王五"]; // 已 autorelease
// 若需長期持有:[p retain]; ... 稍後 [p release];
// 傳統 MRC 寫法
- (void)setName:(NSString *)newName {
if (name != newName) {
[name release]; // 釋放舊值
name = [newName retain]; // 保留新值 (或 copy)
}
}
- (NSString *)name {
return name; // Getter 不 retain,呼叫者若需持有自行 retain
}
關鍵順序:先 retain 新值,再 release 舊值,防止 newName == name 時過早釋放導致崩潰。
// 手動建立池 (非 ARC 環境下常見於大量臨時物件循環)
for (int i = 0; i < 10000; i++) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *tmp = [NSString stringWithFormat:@"Item %d", i];
// 處理 tmp...
[pool drain]; // 釋放池內所有 autorelease 物件
}
| 錯誤代碼 | 問題 | 修正 |
|---|---|---|
name = newName; (無 retain) | 未獲取所有權,外部釋放導致懸空指標 | name = [newName retain]; 或 copy |
[name release]; name = [newName retain]; (順序反) | 若 newName == name,先 release 導致物件銷毀,retain 懸空指標 | 先 retain 再 release |
[convenienceObj release]; | 釋放不屬於自己的物件 (已 autorelease) | 移除 release,或改用 alloc/init |
return [[[Class alloc] init] release]; | 物件即刻銷毀,回傳野指標 | return [[[Class alloc] init] autorelease]; |
自 Xcode 4.2 (LLVM 3.0) 起,ARC 成為預設且強烈推薦。編譯器靜態分析自動插入 retain/release/autorelease,開發者專注物件圖關係。
retain/release/autorelease/dealloc (除非管理非 OC 資源)。strong (預設):保留物件,建立強引用循環風險。weak:不保留,物件銷毀自動置 nil,打破循環引用 (父→子 strong,子→父 weak)。copy:複製不可變副本 (NSString, NSArray, Block 推薦)。unsafe_unretained:不保留、不置 nil,僅用於無法用 weak 的舊版本/標量。NSAutoreleasePool:
@autoreleasepool {
// 大量臨時物件範圍
for (...) { NSString *s = [NSString stringWithFormat:@"%d", i]; }
}
__bridge, __bridge_retained, __bridge_transfer 明確所有權轉移。retain/release/autorelease。dealloc 中的 [ivar release] 移除,僅保留非 OC 資源清理 (如 free(), CFRelease()) 並呼叫 [super dealloc] (ARC 下不寫 [super dealloc])。weak 替代 assign (對物件) 的所有委派/父指標。copy 變為預設 strong (複製到堆上)。-fobjc-arc 編譯旗標,逐檔遷移 (可混編)。// 父持有子
@property (nonatomic, strong) Child *child;
// 子持有父 → 循環引用,雙方永不釋放
// @property (nonatomic, strong) Parent *parent; ❌
// 正確:弱引用
@property (nonatomic, weak) Parent *parent; // ✅
// Block 內捕獲 self → 循環引用
__weak typeof(self) weakSelf = self;
dispatch_async(queue, ^{
typeof(self) strongSelf = weakSelf;
if (strongSelf) [strongSelf doSomething];
});
Objective-C 記憶體管理的核心是所有權傳遞契約。MRC 時代需牢記「誰創建/保留/複製,誰釋放」;ARC 時代將機械操作交給編譯器,開發者專注於物件圖拓撲 (強/弱引用、Block 捕獲、CF 橋接)。無論哪個時代,理解 retainCount 生命週期、autoreleasepool 作用域、dealloc 觸發時機,都是診斷洩漏、崩潰、效能問題的根本能力。