雖然 ARC(自動引用計數)已成主流,但理解 MRC(手動引用計數)仍是掌握 Objective-C 內存模型、排查疑難雜症、閱讀舊代碼庫的基石。本文系統梳理引用計數規則、釋放池機制、@property 屬性修飾符及其背後的存取方法實現。
| 操作 | 方法 | 計數變化 | 備註 |
|---|---|---|---|
| 創建/拷貝 | alloc / new / copy / mutableCopy | +1 | 你擁有該對象,需負責釋放 |
| 保持 | retain | +1 | 聲明擁有權 |
| 釋放 | release | -1 | 放棄擁有權,計數歸零時調用 dealloc |
| 自動釋放 | autorelease | 延遲 -1 | 放入當前 NSAutoreleasePool,池排空時釋放 |
核心原則(Cocoa 內存管理規範):
alloc/copy 創建的對象,你負責 release。retain 的對象,你負責 release。釋放池是 autorelease 對象的容器。池排空(drain / release)時,向池內所有對象發送 release。
// 經典 MRC main 函數範本
int main(int argc, const char *argv[]) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 業務代碼...
// 便利構造器返回的對象已 autorelease
NSArray *arr = [NSArray arrayWithObjects:@"A", @"B", nil];
[pool release]; // 或 [pool drain],排空池並釋放池本身
return 0;
}
注意:autorelease 不會立即改變引用計數,只是在池中登記。若在池排空前對象被 retain,則計數不會歸零。
@property (屬性列表) 類型 名稱;
readwrite(預設):生成 getter + setter。readonly:僅生成 getter。atomic(預設):setter/getter 以鎖保護,線程安全但有性能開銷。nonatomic:無鎖,高性能,iOS 開發慣用。| 修飾符 | 適用類型 | Setter 邏輯示意 | 典型場景 |
|---|---|---|---|
assign | 基本類型、NSInteger、CGRect 等純量 | _ivar = newValue; | 非對象類型;弱引用(易產生懸空指針) |
retain / strong | NSObject 子類 | [newValue retain]; [_ivar release]; _ivar = newValue; | 擁有關係,父持有子、單例持有 |
copy | 遵守 NSCopying 的對象(NSString、NSArray、NSDictionary、Block) | _ivar = [newValue copy]; | 需不可變快照,防止外部修改影響內部狀態 |
// retain 語義
- (void)setName:(NSString *)newName {
if (newName != _name) {
[_name release]; // 先釋放舊值(安全:對 nil 發送 release 無害)
_name = [newName retain]; // 再保持新值
}
}
// copy 語義
- (void)setTitle:(NSString *)newTitle {
if (newTitle != _title) {
[_title release];
_title = [newTitle copy]; // 複製,新對象 retainCount = 1
}
}
只有遵守 NSCopying 協議並實現 -copyWithZone: 的類才能配合 copy 屬性。對於自定義模型類,若需複製語義,必須實現該協議;否則請改用 strong 或提供專門的 copyInstance 方法。
假設 NSString *str = [[NSString alloc] initWithString:@"hello"]; 堆地址 0x1111,棧指針 0x2222 指向 0x1111。
newStr = str; 兩指針同指 0x1111,計數不變。釋放任一導致另一懸空。newStr = [str retain]; 新指針 0x4444 指向 0x1111,計數 +1。雙方各自 release 才安全。newStr = [str copy]; 新建對象 0x3333 內容「hello」,計數 = 1。互不干擾。// Song.h
@interface Song : NSObject
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *artist;
@property (nonatomic, assign) long long duration;
- (instancetype)initWithTitle:(NSString *)t artist:(NSString *)a duration:(long long)d;
@end
// Song.m
@implementation Song
@synthesize title = _title, artist = _artist, duration = _duration;
- (instancetype)initWithTitle:(NSString *)t artist:(NSString *)a duration:(long long)d {
if ((self = [super init])) {
self.title = t; // 呼叫 copy setter
self.artist = a;
self.duration = d;
}
return self;
}
- (void)dealloc {
[_title release];
[_artist release];
[super dealloc];
}
@end
MRC 時代的內存管理規則嚴謹而對稱:誰持有,誰釋放。@property 的修飾符將這套規則編碼為聲明式語法,編譯器自動生成符合規範的存取方法。理解其背後的 retain/release/copy 邏輯,不僅能駕馭舊代碼,更能在 ARC 環境下精準控制對象圖生命週期、解決循環引用與性能瓶頸。