雖然現代 iOS 使用 ARC,但理解循環引用本質對調試洩漏、處理 Core Foundation/跨語言橋接仍至關重要。
// Dog.h @interface Dog : NSObject @property (nonatomic, assign) Person *owner; // assign 不增加引用計數 - (void)dealloc; @end // Person.h @interface Person : NSObject @property (nonatomic, retain) Dog *dog; // retain 增加引用計數 - (void)dealloc; @end
// Dog.m
@implementation Dog
- (void)dealloc { NSLog(@"Dog dealloc"); [super dealloc]; }
@end
// Person.m
@implementation Person
- (void)dealloc { [_dog release]; NSLog(@"Person dealloc"); [super dealloc]; }
@end
// main.m
int main() {
Person *p = [[Person alloc] init]; // retainCount = 1
Dog *d = [[Dog alloc] init]; // retainCount = 1
p.dog = d; // d retainCount = 2
d.owner = p; // p retainCount = 2 (assign 不增)
[p release]; // p = 1
[d release]; // d = 1
// 此時雙方 retainCount 均為 1,dealloc 未調用 → 洩漏
NSLog(@"End");
return 0;
}
p.dog = d → d retainCount +1d.owner = p (assign) → p retainCount 不變p=1, d=1,雙方仍持有對方,**引用計數永不歸零**dealloc 不執行 → [_dog release] 不執行 → 死鎖// Dog.h 修正 @property (nonatomic, assign) Person *owner; // assign / weak 皆可打破環 // 或 ARC 寫法 @property (nonatomic, weak) Person *owner;
原則:父→子 strong/retain,子→父 weak/assign。
// ARC 下同樣會洩漏 @property (nonatomic, strong) Dog *dog; @property (nonatomic, strong) Person *owner; // 雙向 strong → 環 // 修正 @property (nonatomic, weak) Person *owner;
ARC 編譯器會對 weak 自動置零,對象釋放時自動斷開引用。
| 工具/方法 | 用途 |
|---|---|
| Instruments → Leaks | 運行時自動檢測循環引用洩漏 |
| Debug Memory Graph (Xcode 8+) | 可視化對象引用圖,高亮循環 |
| Analyze (Shift+Cmd+B) | 靜態分析潛在洩漏 |
| 命名規範 | delegate、block 回調、父指針統一用 weak |
__weak typeof(self) weakSelf = self;
self.completion = ^{ [weakSelf handleResult]; };
// 或
__block typeof(self) blockSelf = self; // MRC 手動置 nil
self.completion = ^{ [blockSelf handleResult]; blockSelf = nil; };
循環引用本質是強引用環導致引用計數永不歸零。解決核心:**識別所有權層級,父強子弱,代理/塊/回調用弱引用**。掌握 Instruments Leaks、Memory Graph 兩大檢測神器,配合命名規範與代碼審查,能從根源消除內存洩漏。