更换rootViewController后旧视图仍在视图层级,有人遇到过该问题吗?
我之前也碰到过一模一样的情况!这种现象看起来有点诡异,但其实是因为我们对UIWindow管理RootViewController的机制理解得不够透彻,加上手动操作反而打乱了系统的默认流程导致的。
问题分析
你代码里手动移除window的subviews、旧RootViewController的视图以及从父控制器移除的操作,其实是多余且可能起反作用的。UIWindow在设置rootViewController时,系统本身就会自动处理旧控制器的生命周期(比如调用viewWillDisappear、viewDidDisappear),并将其视图从window中移除。但你的手动操作可能导致旧视图被window的某个内部私有容器引用持有,或者因为强引用没被释放,所以在Debug View Hierarchy里还能看到它,但它已经不是window的subviews了——这也和你控制台的输出完全对应上。
解决方案
1. 简化代码,依赖系统默认处理
直接去掉那些手动移除的代码,只保留设置RootViewController的核心逻辑,系统会帮你处理好所有清理工作:
if let appDelegate = UIApplication.shared.delegate as? AppDelegate { appDelegate.window?.rootViewController = newRootViewController // 可选:如果需要确保window处于激活状态,加上这句 appDelegate.window?.makeKeyAndVisible() }
这样修改后,系统会自动完成旧RootViewController视图的移除和生命周期收尾,避免手动操作带来的引用残留问题。
2. 检查是否存在强引用导致旧控制器未释放
如果简化代码后Debug View里还是能看到旧视图,那大概率是旧的RootViewController被其他地方强引用了:
- 检查是否有未移除的通知观察者
- 检查闭包是否捕获了旧控制器的强引用(比如用了
self而没加[weak self]) - 检查全局变量、单例是否持有了旧控制器的引用
这些强引用会导致控制器无法被ARC释放,它的视图自然也会留在内存中,Debug工具就能追踪到它。
3. 适配iOS 13+的Scene场景(如果你的项目是新版本)
如果你的App适配了iOS 13及以上的多Scene模式,window是由SceneDelegate管理的,而不是AppDelegate,这时候要从SceneDelegate获取window:
if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let sceneDelegate = scene.delegate as? SceneDelegate { sceneDelegate.window?.rootViewController = newRootViewController sceneDelegate.window?.makeKeyAndVisible() }
补充说明
你看到的旧视图不在window的subviews里但还能在Debug View里看到,本质是它已经从视图层级中被移除了,但内存还没被释放。这种情况不会影响屏幕显示,但会造成不必要的内存占用,所以按照上面的方案修复后就能解决。
内容的提问来源于stack exchange,提问作者Anh Pham

