iOS开发:dismiss子控制器后如何保留父控制器原状态?
首先得说,你遇到的这个情况其实不太符合iOS控制器present/dismiss的常规行为——正常来说,present只是把子控制器叠在父控制器的视图层级上,dismiss后父控制器的实例应该完全保留,属性不会被重置。所以核心问题大概率是你的父控制器被意外销毁并重新初始化了,或者代码里有某些逻辑导致状态被重置。
我给你梳理几个排查和解决的方向:
1. 先确认父控制器是不是真的被重建了
先给ParentViewController加个deinit方法打印日志,看看dismiss的时候父控制器是不是被销毁了:
deinit { print("ParentViewController 被销毁了!") }
如果dismiss的时候控制台输出了这句话,那说明你的父控制器实例确实被回收了,之后触发的viewWillAppear其实是新创建的父控制器实例的生命周期方法——这时候你得去查代码里哪里不小心重新初始化了ParentViewController,比如:
- 是不是在获取
TestViewController的时候,误操作加载了父控制器的新实例? - 你的父控制器是不是嵌入在某个容器(比如导航控制器、TabBar控制器)里,容器的逻辑导致它被销毁重建?
- 有没有遇到内存警告,系统自动回收了后台的父控制器?
2. 修正Present/Dismiss的调用逻辑
看你的代码,子控制器的dismissController闭包里调用了self.dismiss——这里的self是父控制器,这个逻辑是对的(因为present操作是父控制器发起的,由父控制器调用dismiss才能正确关闭子控制器)。但要确认子控制器内部有没有额外调用self.dismiss(),如果两边都调用,可能会导致层级混乱,不过一般不会直接引发状态丢失,但还是要排查。
另外,检查你获取TestViewController的方式:如果是从Storyboard加载,要确保是正确的实例,比如:
// 正确的方式:直接实例化子控制器 let presentViewController = storyboard?.instantiateViewController(withIdentifier: "TestViewController") as! TestViewController
不要不小心加载了父控制器的新实例,这是新手常犯的错误。
3. 持久化关键状态(应对系统回收场景)
如果排查后发现是内存警告导致系统回收了父控制器,那就要把需要保留的状态(比如items、screenLoaded)持久化保存。这里推荐用UserDefaults(适合轻量数据),或者CoreData(适合大量数据)。
举个用UserDefaults的例子,首先让你的Item类遵循Codable协议(这样才能序列化):
struct Item: Codable { // 你的Item属性 let id: String // ...其他属性 }
然后在父控制器里添加状态保存和恢复的逻辑:
class ParentViewController: UIViewController { private var items = [String:[Item]]() private var screenLoaded: Bool = false override func viewDidLoad() { super.viewDidLoad() // 从UserDefaults恢复状态 restoreState() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // 保存状态到UserDefaults saveState() } private func saveState() { // 保存screenLoaded UserDefaults.standard.set(screenLoaded, forKey: "ParentScreenLoaded") // 保存items(需要Item遵循Codable) do { let data = try JSONEncoder().encode(items) UserDefaults.standard.set(data, forKey: "ParentItems") } catch { print("保存items失败:\(error)") } } private func restoreState() { screenLoaded = UserDefaults.standard.bool(forKey: "ParentScreenLoaded") if let data = UserDefaults.standard.data(forKey: "ParentItems") { do { items = try JSONDecoder().decode([String:[Item]].self, from: data) } catch { print("恢复items失败:\(error)") items = [:] } } } // ...其他代码 }
这样即使父控制器被系统回收,下次重建时也能恢复之前的状态。
4. 检查全局变量/属性的初始化逻辑
最后再确认一下你的items和screenLoaded的初始化逻辑——有没有在其他地方(比如viewWillAppear)不小心把它们重置为初始值?比如如果viewWillAppear里有items = [:]这种代码,那每次触发都会清空,你提供的代码里没有,但还是要排查整个文件的其他逻辑。
内容的提问来源于stack exchange,提问作者thus

