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

MVVM+Coordinator架构下SplashscreenViewController未销毁问题求助

排查SplashscreenViewController无法销毁的循环引用问题

我太懂这种找不到内存泄漏根源的头疼了——启动页控制器没法销毁,不仅占内存,还可能引发后续的逻辑问题。先从你给出的代码片段入手,再给你几个高频踩坑点和排查方法:

1. 先检查ViewModel的引用关系

你代码里定义了var viewModel: SplashscreenViewModelType!,这是VC对ViewModel的强引用。如果你的ViewModel反过来也强引用了当前VC(比如ViewModel里写了var viewController: SplashscreenViewController?),那直接就形成了循环引用:VC → ViewModel → VC,两者都没法被释放。

解决思路:ViewModel对VC的引用必须是弱引用,改成weak var viewController: SplashscreenViewController?,或者用闭包回调的方式(闭包里要捕获weak self)。

2. 动画/闭包的self捕获是重灾区

启动页通常会有动画逻辑,如果你用了UIView动画、Completion闭包或者其他异步任务,闭包里直接写self很容易触发循环引用。比如类似这样的代码:

UIView.animate(withDuration: animationDuration) {
    self.view.transform = CGAffineTransform(scaleX: self.animationEndScale, y: self.animationEndScale)
} completion: { finished in
    self.viewModel.notifyAnimationComplete()
}

这里闭包会强捕获self,而如果闭包被系统动画队列持有,同时VC又持有ViewModel,ViewModel再持有VC的话,就会形成闭环。

解决思路:闭包里一定要用[weak self](或者[unowned self],但weak更安全):

UIView.animate(withDuration: animationDuration) { [weak self] in
    guard let self = self else { return }
    self.view.transform = CGAffineTransform(scaleX: self.animationEndScale, y: self.animationEndScale)
} completion: { [weak self] finished in
    self?.viewModel.notifyAnimationComplete()
}

3. 其他可能的强引用来源

  • 定时器(Timer):如果VC创建了Timer,并且把target设为self,Timer会被RunLoop强引用,同时VC又持有Timer的话,就会循环。一定要在deinit里调用timer.invalidate()。
  • 通知中心观察者:虽然iOS 9+系统会自动移除通知观察者,但如果是自定义的通知中心,或者注册时用了block方式且捕获了self,还是可能有问题。建议在deinit里主动移除观察者:
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
  • 闭包属性:如果VC有自己的闭包属性,且闭包里捕获了self,同时这个闭包被外部对象持有,也会导致循环。比如:
    var onDismiss: (() -> Void)?
    
    // 外部赋值时如果捕获了self,就有风险
    onDismiss = {
        self.navigationController?.popViewController(animated: true)
    }
    
    同样要改成[weak self]。

快速定位问题的工具

别光靠Malloc栈,用Xcode的Memory Graph Debugger更直接:

  1. 运行App,进入启动页后跳转到其他页面
  2. 点击Debug工具栏里的内存图按钮(📊)
  3. 在内存图里找到SplashscreenViewController的实例,查看它的「Retained By」引用链,就能清楚看到是谁还在持有它的强引用。

另外,给VC加个deinit方法验证:

deinit {
    print("✅ SplashscreenViewController 已销毁")
}

如果控制台没打这个日志,说明确实存在强引用没释放。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 09:41:18