51mee - AI智能招聘平台Logo
模拟面试题目大全招聘中心会员专区

在iOS开发中,如何处理循环引用(例如ViewModel与ViewController通过闭包相互引用,或通过KVO监听导致循环引用),并解释不同场景下选择weak、unowned或asynchronous闭包的原因?

游卡iOS开发难度:中等

答案

1) 【一句话结论】iOS中循环引用会导致内存泄漏,需通过弱引用(weak)、无主引用(unowned)或异步闭包(@escaping)断开强引用链,其中弱引用适用于可能被释放的对象,无主引用适用于确定不会释放的对象,异步闭包用于避免强引用循环。

2) 【原理/概念讲解】老师现在解释一下核心概念:首先,强引用循环(retain cycle) 是指两个或多个对象互相持有对方(比如A持有B,B也持有A),系统无法在对象不再使用时释放它们,导致内存泄漏。在iOS中,闭包默认捕获外部变量为强引用(strong),因此如果ViewModel持有ViewController的闭包,而ViewController又持有ViewModel的闭包,就会形成循环。KVO(Key-Value Observing)的循环引用则是观察者(比如ViewController)和被观察对象(比如ViewModel)相互持有,导致两者都无法释放。解决方法是断开强引用链:

  • 弱引用(weak):指针指向对象,对象释放后指针自动置nil,适用于可能被释放的对象(比如视图控制器在视图消失时被释放);
  • 无主引用(unowned):指针指向对象,对象释放后指针保持原值,适用于确定不会释放的对象(比如单例ViewModel);
  • 异步闭包(@escaping):允许闭包在异步操作完成后执行,通过弱引用结合异步闭包,避免强引用循环(比如网络请求回调中,闭包捕获的ViewModel用weak)。

可以用个类比:强引用循环像两个朋友互相拿着对方的手,谁也不放手,系统不知道该释放谁,导致内存泄漏。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) 【追问清单】

  • 问:weak和unowned的区别是什么?当对象可能被释放时,为什么用weak而不是unowned?
    回答要点:weak是弱引用,对象释放后指针置nil,适用于可能被释放的对象;unowned是强引用,对象释放后指针保持原值,适用于确定不会释放的对象,否则会导致空指针。
  • 问:KVO的循环引用怎么处理?为什么用weak闭包?
    回答要点:KVO中观察者(如ViewController)和被观察对象(如ViewModel)相互持有,使用weak闭包(弱引用)避免循环,对象释放后weak指针置nil,不会导致强引用循环。
  • 问:asynchronous闭包(@escaping)具体怎么用?为什么能避免循环?
    回答要点:@escaping允许闭包在异步操作完成后执行,通过弱引用结合异步闭包,避免闭包持有强引用,从而避免循环(比如网络请求回调中,闭包捕获的ViewModel用weak)。
  • 问:如果忘记在asynchronous闭包中使用weak,会发生什么?
    回答要点:会导致强引用循环,对象无法被释放,导致内存泄漏,系统无法回收内存。
  • 问:有没有工具可以检测循环引用?
    回答要点:Xcode的Instruments工具中的Leaks或Alamofire的内存泄漏检测工具,可以帮助检测循环引用。

7) 【常见坑/雷区】

  • 弱引用和unowned混用:比如对象可能被释放时用了unowned,导致空指针或崩溃;
  • 忘记在asynchronous闭包中使用weak:比如异步操作中闭包捕获的变量用了strong,导致强引用循环;
  • KVO的循环引用处理不当:比如没有使用weak闭包,导致观察者和被观察对象无法释放;
  • 闭包捕获的默认行为:默认是strong,容易忽略,导致循环;
  • unowned闭包的使用场景判断错误:比如对象确定不会释放时用了weak,导致循环。
51mee.com致力于为招聘者提供最新、最全的招聘信息。AI智能解析岗位要求,聚合全网优质机会。
产品招聘中心面经会员专区简历解析Resume API
联系我们南京浅度求索科技有限公司admin@51mee.com
联系客服
51mee客服微信二维码 - 扫码添加客服获取帮助
© 2025 南京浅度求索科技有限公司. All rights reserved.
公安备案图标苏公网安备32010602012192号苏ICP备2025178433号-1