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

Objective-C 記憶體管理精要:引用計數、所有權規則與 ARC 時代最佳實務

     編輯:關於C語言

核心機制:引用計數 (Reference Counting)

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])。

所有權規則 (Ownership Policy)

方法名稱前綴回傳物件所有權呼叫者責任
alloc, new, copy, mutableCopy擁有 (retainCount=1)必須 release 或 autorelease
其他 (如 stringWithFormat:, arrayWithObject:)不擁有 (已 autorelease)若需持久持有,呼叫 retain;否則無需釋放

口訣:「你創建/保留/複製 → 你負責釋放;你只是借用 → 不要釋放。」

實作模式詳解

1. 建立與釋放基本模式

// 正確:建立後立即使用,用完釋放
Person *p = [[Person alloc] initWithName:@"張三"];
NSLog(@"%@", p.name);
[p release];  // 配對 alloc

// 錯誤:未釋放 → 記憶體洩漏
Person *p2 = [[Person alloc] initWithName:@"李四"];
// 直接跳過 release

// 錯誤:雙重釋放 → 崩潰 (double free)
[p release];
[p release];

2. 便利建構器 回傳自動釋放物件

// 類別方法實作
+ (instancetype)personWithName:(NSString *)name {
    Person *p = [[Person alloc] initWithName:name];
    return [p autorelease];  // 關鍵:放入自動釋放池,延遲釋放
}

// 呼叫端:無需 release
Person *p = [Person personWithName:@"王五"];  // 已 autorelease
// 若需長期持有:[p retain]; ... 稍後 [p release];

3. 存取器 實作模式 (Setter/Getter)

// 傳統 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 時過早釋放導致崩潰。

4. 延遲釋放池 使用時機

// 手動建立池 (非 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];

ARC (Automatic Reference Counting) 時代的變化

自 Xcode 4.2 (LLVM 3.0) 起,ARC 成為預設且強烈推薦。編譯器靜態分析自動插入 retain/release/autorelease,開發者專注物件圖關係。

ARC 關鍵規則

  • 不再手寫 retain/release/autorelease/dealloc (除非管理非 OC 資源)。
  • 屬性修飾詞:
    • strong (預設):保留物件,建立強引用循環風險。
    • weak:不保留,物件銷毀自動置 nil,打破循環引用 (父→子 strong,子→父 weak)。
    • copy:複製不可變副本 (NSString, NSArray, Block 推薦)。
    • unsafe_unretained:不保留、不置 nil,僅用於無法用 weak 的舊版本/標量。
  • @autoreleasepool 區塊 替代 NSAutoreleasePool:
    @autoreleasepool {
        // 大量臨時物件範圍
        for (...) { NSString *s = [NSString stringWithFormat:@"%d", i]; }
    }
  • Core Foundation / C 橋接:__bridge, __bridge_retained, __bridge_transfer 明確所有權轉移。

MRC 專案遷移 ARC 檢查清單

  1. 移除所有手動 retain/release/autorelease。
  2. 將 dealloc 中的 [ivar release] 移除,僅保留非 OC 資源清理 (如 free(), CFRelease()) 並呼叫 [super dealloc] (ARC 下不寫 [super dealloc])。
  3. 檢查 weak 替代 assign (對物件) 的所有委派/父指標。
  4. Block 從 copy 變為預設 strong (複製到堆上)。
  5. 啟用 -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 觸發時機,都是診斷洩漏、崩潰、效能問題的根本能力。

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