You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何显式赋值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") }
}
  1. 在do作用域内,pet是栈上的Optional变量,指向新建的Animal对象。
  2. 给pet?.command赋值闭包时,闭包要捕获可变的pet变量,Swift会自动把pet从栈上移到堆上(装箱),延长它的生命周期。
  3. 此时形成循环引用链:Animal对象 → 闭包(command属性) → 堆上的pet变量 → Animal对象。
  4. 当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
}
  1. 前两步和第一段一致:pet被闭包捕获并移到堆上,形成初始的引用链。
  2. 执行pet = nil时,操作的是堆上被捕获的pet变量,将它的Optional值设为nil——这意味着pet变量不再指向Animal对象。
  3. 此时循环引用链被打破:Animal对象持有闭包,但闭包持有的pet变量已不再指向Animal。ARC检测到Animal对象的强引用计数变为0,调用deinit释放对象。
  4. Animal对象释放后,它持有的闭包也会被释放,最终堆上的pet变量也会被清理。

关键差异总结

  • 作用域结束仅销毁栈上的原变量,但堆上被闭包捕获的pet变量不受影响,依然保持对Animal的强引用,循环引用持续存在。
  • 手动赋值nil是直接修改堆上被捕获的pet变量,切断它与Animal对象的关联,打破循环引用,让ARC能正常回收对象。

内容的提问来源于stack exchange,提问作者user21622060

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 14:07:16