Swift中[unowned self]放置位置困惑及循环引用问题解疑
嘿,我来帮你搞清楚这两个场景下循环引用的问题,以及[unowned self]该怎么放!首先得明确:循环引用的本质是两个对象互相强引用,形成无法打破的引用闭环,导致它们的deinit都不会被调用。下面我用具体代码示例拆解两种常见场景:
Case 1:闭包是ViewController的直接属性
这种场景里,ViewController直接持有一个闭包属性,而闭包内部又引用了self(ViewController),形成闭环。
有问题的代码:
class ViewController: UIViewController { // 闭包属性:默认是强引用 var onCompletion: (() -> Void)? override func viewDidLoad() { super.viewDidLoad() // 闭包内部强引用self,形成循环 onCompletion = { print("当前页面标题:\(self.title ?? "无标题")") } } deinit { print("ViewController 被释放了!") } }
循环链条:ViewController → 强持有 onCompletion 闭包 → 闭包强持有 self(ViewController)→ 闭环形成,两者都无法释放。
正确的解决方式:
把[unowned self]加到闭包的捕获列表里,让闭包不再强引用ViewController:
onCompletion = { [unowned self] in print("当前页面标题:\(self.title ?? "无标题")") }
这样闭环被打破,当ViewController被销毁时,闭包也会被释放,deinit就能正常调用了。
Case 2:闭包被其他对象持有,且ViewController持有该对象
这种场景里,ViewController强持有一个第三方对象(比如网络管理器、工具类),然后调用该对象的方法时传入闭包,闭包内部引用self,形成跨对象的循环引用。
有问题的代码:
class APIManager { // @escaping 表示闭包会被保存下来(非立即执行) func fetchData(completion: @escaping () -> Void) { // 模拟异步任务 DispatchQueue.global().asyncAfter(deadline: .now() + 1) { completion() } } } class ViewController: UIViewController { // VC强持有APIManager实例 let apiManager = APIManager() override func viewDidLoad() { super.viewDidLoad() // 传入的闭包强引用self,形成循环 apiManager.fetchData { print("数据加载完成,当前页面标题:\(self.title ?? "无标题")") } } deinit { print("ViewController 被释放了!") } }
循环链条:ViewController → 强持有 apiManager → apiManager 强持有 completion 闭包 → 闭包强持有 self(ViewController)→ 闭环形成。
正确的解决方式:
同样把[unowned self]加到传入的闭包捕获列表里,而不是加到APIManager内部的闭包:
apiManager.fetchData { [unowned self] in print("数据加载完成,当前页面标题:\(self.title ?? "无标题")") }
这样闭包不再强引用ViewController,闭环被打破,deinit就能正常触发。
为什么会出现“一种有效一种无效”?
大概率是你把[unowned self]加错了位置:
- 如果在Case1里,你没把
[unowned self]加到ViewController自己的闭包属性里,而是加在了其他地方,肯定无效; - 如果在Case2里,你错误地把
[unowned self]加到了APIManager内部的闭包(比如DispatchQueue.global().asyncAfter里的闭包),而不是加到传入的completion闭包,那也无法打破循环——因为真正强引用self的是你传入的那个闭包,不是APIManager内部的闭包。
额外提醒:unowned vs weak
- 用
[unowned self]的前提是:闭包执行期间,self绝对不会被释放(比如ViewController肯定在闭包执行时还存在); - 如果self可能在闭包执行前就被释放(比如页面被提前pop),建议用
[weak self],并在闭包内部判断self是否为nil,避免野指针崩溃。
内容的提问来源于stack exchange,提问作者Kesong Xie

