循環引用是手動引用計數(MRC)時代最常見的內存洩漏來源。當兩個對象互相 強引用(retain)對方時,釋放任一對象都無法將計數降至零,導致 dealloc 永不調用,內存永不回收。
// Dog.h
@property (nonatomic, assign, readwrite) Person *person; // 關鍵:assign 不 retain
// Dog.m
- (void)dealloc { NSLog(@"dog dealloc"); [super dealloc]; }
// Person.h
@property (nonatomic, retain, readwrite) Dog *dog; // 強引用
// Person.m
- (void)dealloc { [_dog release]; NSLog(@"Person dealloc"); [super dealloc]; }
// main.m
Person *p = [[Person alloc] init]; // p 計數=1
Dog *dog = [[Dog alloc] init]; // dog 計數=1
[p setDog:dog]; // dog 計數=2
[dog setPerson:p]; // p 計數=2
[p release]; // p 計數=1
[dog release]; // dog 計數=1
// 此時雙方計數均為1,dealloc 未執行,互相等待 → 洩漏
NSLog(@"is over"); // 程序結束前對象未釋放
Person retain Dog,Dog retain Person(若 Dog 屬性用 retain)。dealloc 不被調用,內部 release 代碼不執行,形成死鎖。assign(MRC)或 weak(ARC)。Dog.person 定義為 assign,不增加 Person 計數。Dog.dealloc 中不調用 [_person release]。retain/strong)。assign/weak)。assign → weak(自動置 nil)或 unsafe_unretained(不置 nil)。weak 修飾的屬性在對象銷毀時自動清零,更安全。循環引用本質是所有權邏輯混淆。明確「誰擁有誰」,在非擁有方使用弱引用,是解決之道。