为何显式赋值nil能打破Swift强引用循环?
Swift闭包强引用:手动赋值nil与作用域销毁的差异解析
核心前提:闭包捕获的是变量本身,而非对象引用
首先要纠正一个关键认知:你代码里的闭包{ print(pet?.name ?? "Bobby") }捕获的不是pet指向的Animal对象,而是**pet这个Optional变量本身**(因为pet是var声明的可变变量,闭包会以引用方式捕获它)。
第一段代码(无手动赋值nil)的内存泄漏逻辑
先看这段代码:
class Animal { var name = "Fischer" var command: () -> Void = { } deinit { print(#function, #line) } } do { var pet: Animal? = Animal() pet?.command = { print(pet?.name ?? "Bobby") } }
- 在
do作用域内,pet是栈上的Optional变量,指向新建的Animal对象。 - 给
pet?.command赋值闭包时,闭包要捕获可变的pet变量,Swift会自动把pet从栈上移到堆上(装箱),延长它的生命周期。 - 此时形成循环引用链:Animal对象 → 闭包(command属性) → 堆上的pet变量 → Animal对象。
- 当
do作用域结束时,栈上的原pet变量标记为可销毁,但堆上被闭包捕获的pet变量依然存在,且保持着对Animal对象的强引用。循环引用未被打破,ARC无法释放Animal对象,导致内存泄漏。
第二段代码(手动赋值nil)的对象释放逻辑
再看这段代码:
class Animal { var name = "Fischer" var command: () -> Void = { } deinit { print(#function, #line) } } do { var pet: Animal? = Animal() pet?.command = { print(pet?.name ?? "Bobby") } pet = nil }
- 前两步和第一段一致:
pet被闭包捕获并移到堆上,形成初始的引用链。 - 执行
pet = nil时,操作的是堆上被捕获的pet变量,将它的Optional值设为nil——这意味着pet变量不再指向Animal对象。 - 此时循环引用链被打破:Animal对象持有闭包,但闭包持有的
pet变量已不再指向Animal。ARC检测到Animal对象的强引用计数变为0,调用deinit释放对象。 - Animal对象释放后,它持有的闭包也会被释放,最终堆上的
pet变量也会被清理。
关键差异总结
- 作用域结束仅销毁栈上的原变量,但堆上被闭包捕获的
pet变量不受影响,依然保持对Animal的强引用,循环引用持续存在。 - 手动赋值
nil是直接修改堆上被捕获的pet变量,切断它与Animal对象的关联,打破循环引用,让ARC能正常回收对象。
内容的提问来源于stack exchange,提问作者user21622060
相关产品推荐
相关产品推荐

