
1) 【一句话结论】iOS中循环引用会导致内存泄漏,需通过弱引用(weak)、无主引用(unowned)或异步闭包(@escaping)断开强引用链,其中弱引用适用于可能被释放的对象,无主引用适用于确定不会释放的对象,异步闭包用于避免强引用循环。
2) 【原理/概念讲解】老师现在解释一下核心概念:首先,强引用循环(retain cycle) 是指两个或多个对象互相持有对方(比如A持有B,B也持有A),系统无法在对象不再使用时释放它们,导致内存泄漏。在iOS中,闭包默认捕获外部变量为强引用(strong),因此如果ViewModel持有ViewController的闭包,而ViewController又持有ViewModel的闭包,就会形成循环。KVO(Key-Value Observing)的循环引用则是观察者(比如ViewController)和被观察对象(比如ViewModel)相互持有,导致两者都无法释放。解决方法是断开强引用链:
可以用个类比:强引用循环像两个朋友互相拿着对方的手,谁也不放手,系统不知道该释放谁,导致内存泄漏。KVO的循环引用就像观察者(A)拿着被观察对象(B),B又拿着A,形成闭环,必须断开其中一个“手”的连接。
3) 【对比与适用场景】
| 引用类型 | 定义 | 特性 | 使用场景 | 注意点 |
|---|---|---|---|---|
| weak | 弱引用 | 对象释放后指针自动置nil | 闭包捕获可能被释放的对象(如ViewController持有ViewModel的weak闭包) | 必须确保对象不会提前释放,否则可能导致空指针 |
| unowned | 无主引用 | 对象释放后指针保持原值 | 闭包捕获确定不会释放的对象(如ViewModel持有ViewController的unowned闭包) | 必须确保对象不会释放,否则会导致空指针或崩溃 |
| asynchronous闭包(@escaping) | 允许闭包在异步操作完成后执行 | 结合弱引用,避免强引用循环 | 异步操作中,闭包捕获的变量用weak(如网络请求回调) | 需要明确闭包在异步操作完成后执行,否则可能导致循环 |
4) 【示例】
假设ViewModel和ViewController通过闭包相互引用,ViewModel持有ViewController的weak闭包,ViewController持有ViewModel的weak闭包:
class ViewModel {
var viewController: ViewController?
var disposeBag = DisposeBag()
func setup() {
viewController = ViewController()
viewController?.viewModel = self
// 使用weak闭包
viewController?.onViewModelChange = { [weak self] in
guard let strongSelf = self else { return }
// 处理逻辑
}
}
}
class ViewController: UIViewController {
var viewModel: ViewModel?
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
viewModel?.onViewModelChange = { [weak self] in
guard let strongSelf = self else { return }
// 处理逻辑
}
}
}
这里,ViewModel和ViewController通过weak闭包相互引用,当ViewController释放时,ViewModel的weak指针会置nil,不会持有ViewController,从而避免循环。
5) 【面试口播版答案】
各位面试官好,关于iOS中循环引用的处理,核心是避免强引用循环导致内存泄漏。首先,循环引用常见场景包括闭包相互引用(如ViewModel和ViewController通过闭包双向持有)和KVO监听(观察者与被观察对象相互持有)。解决方法是使用weak、unowned或异步闭包断开强引用链。
对于闭包相互引用,默认闭包捕获外部变量为strong,会导致循环。此时,如果对象可能被释放(比如ViewController在视图消失时被释放),应使用weak闭包(弱引用),对象释放后weak指针置nil,不会导致循环;如果对象确定不会释放(比如ViewModel是单例),则使用unowned闭包(无主引用),直接持有对象,无需置nil。另外,异步闭包(@escaping)允许闭包在异步操作完成后执行,通过弱引用结合异步闭包,避免强引用循环(比如网络请求回调中,闭包捕获的ViewModel用weak)。
举个例子,ViewModel和ViewController通过weak闭包相互引用,ViewModel持有ViewController的weak闭包,ViewController持有ViewModel的weak闭包,这样当ViewController释放时,ViewModel的weak指针会置nil,不会持有ViewController,从而避免循环。
KVO的循环引用处理类似,观察者(ViewController)和被观察对象(ViewModel)相互持有,使用weak闭包避免循环,对象释放后weak指针置nil,不会导致强引用循环。
忘记在闭包中使用weak会导致强引用循环,对象无法被释放,导致内存泄漏,系统无法回收内存。Xcode的Leaks工具可以帮助检测循环引用。
6) 【追问清单】
7) 【常见坑/雷区】