RouterDashboardViewController未从内存释放问题排查求助
问题拆解
你碰到的情况很典型:RouterDashboardViewController作为导航栈里的原根视图,在重置导航控制器的根视图后,实例居然没被释放(deinit完全没触发),还伴随着viewDidLoad()被多次调用的问题。这几乎可以肯定是强引用循环或者被其他外部对象意外强持有导致的,咱们一步步来排查:
核心排查方向(按优先级排序)
1. 先查闭包的强引用问题
这是内存泄漏的重灾区!如果你的RouterDashboardViewController里用了闭包——不管是网络请求的回调、按钮的点击事件,还是Timer的闭包——一定要检查有没有不小心强引用了self。举个例子:
// 踩坑写法:闭包会强持有self,形成循环引用 refreshControl.addTarget { self.loadDashboardData() }
正确的姿势是用weak self(或者unowned self,但前者更安全)打破循环:
refreshControl.addTarget { [weak self] in self?.loadDashboardData() }
2. 检查委托(Delegate)的引用类型
很多开发者会犯这个错:把委托属性声明成默认的强引用,导致被委托对象反过来强持有视图控制器。比如:
// 错误写法:强引用委托,会导致循环引用 var dashboardDelegate: DashboardProtocol?
必须改成弱引用:
// 正确写法:弱引用委托,避免循环 weak var dashboardDelegate: DashboardProtocol?
注意:委托协议必须是class类型(在协议前加class关键字),不然不能用weak修饰。
3. 清理Timer和NotificationCenter订阅
- Timer:如果你的控制器创建了Timer,一定要在销毁前调用
invalidate()!因为Timer会强持有目标对象,哪怕你把控制器从导航栈移除,Timer没失效的话,控制器就永远不会被释放。可以在viewWillDisappear或者deinit里处理:override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) dashboardTimer?.invalidate() dashboardTimer = nil } - NotificationCenter:如果订阅了通知,一定要记得移除观察者!虽然iOS 9+会自动移除,但保险起见还是手动处理,避免不必要的持有:
override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) NotificationCenter.default.removeObserver(self) }
4. 检查导航栈重置的代码逻辑
你重置导航根视图的代码是不是正确?如果只是直接修改navigationController?.rootViewController,旧的控制器可能还留在导航栈的历史记录里,导致被持有。正确的重置方式应该是替换整个导航栈:
// 正确:直接替换导航栈的所有视图控制器,彻底移除旧实例 let newRootVC = NewRootViewController() navigationController?.setViewControllers([newRootVC], animated: true)
关于viewDidLoad多次调用的解释
viewDidLoad()被多次调用,要么是你的RouterDashboardViewController被多次实例化(比如重置导航栈时不小心重复创建了实例),要么是控制器的视图因为内存警告被系统销毁,当再次需要显示时,视图会被重新加载,viewDidLoad()就会再次触发。你可以在didReceiveMemoryWarning里加个日志,确认是不是内存警告导致的。
用Instruments精准定位泄漏源
你提供的栈追踪图是关键:在Instruments的Leaks工具里,找到那个未释放的RouterDashboardViewController实例,查看它的Retain Tree(引用树),里面会列出所有强持有它的对象——找到这些对象,就能直接定位泄漏的根源了。
内容的提问来源于stack exchange,提问作者Sumit Jangra

