执行unwind segue返回后,UISplitViewController出现EXC_BAD_ACCESS错误
解决登出后UISplitViewController出现EXC_BAD_ACCESS的问题
嘿,这个场景我之前帮朋友排查过类似的坑!EXC_BAD_ACCESS本质是访问了已释放的内存,结合你描述的登出流程(unwind segue+重置根VC),大概率是旧视图控制器的内存引用没清理干净,或者unwind和根VC重置的操作顺序冲突导致的。下面给你拆解具体原因和解决办法:
可能的原因
- 你同时用了unwind segue和手动重置根VC,两种操作都会触发视图栈的变更,导致系统对旧VC的释放逻辑混乱,SplitVC的子控制器可能还持有已释放对象的引用。
- UISplitViewController或其内嵌的表格VC的代理/数据源用了强引用(没有加
weak),导致旧VC无法被ARC正确释放,根VC替换后这些悬空引用被访问就会崩溃。 - 旧VC的通知观察者、网络请求等资源没及时清理,导致回调时访问了已释放的对象。
具体解决方案
1. 二选一:要么用unwind,要么手动重置根VC
不要同时执行两种视图栈变更操作,建议保留一种更可控的方式:
方案A:直接手动重置根VC(推荐)
去掉unwind segue,在登出按钮的点击事件里直接处理,同时提前清理所有可能的强引用:
@IBAction func logoutButtonTapped(_ sender: UIButton) { // 第一步:清理旧VC的所有强引用和资源 cleanupAllOldReferences() // 第二步:实例化新的登录VC let mainStoryboard = UIStoryboard(name: "Main", bundle: nil) guard let loginVC = mainStoryboard.instantiateViewController(withIdentifier: "LoginVC") as? LoginViewController else { return } // 第三步:重置根VC并刷新界面 if let window = UIApplication.shared.windows.first { window.rootViewController = loginVC window.makeKeyAndVisible() } } // 递归清理所有旧VC的引用 private func cleanupAllOldReferences() { guard let oldRootVC = UIApplication.shared.windows.first?.rootViewController else { return } func cleanup(_ vc: UIViewController) { // 移除通知观察者 NotificationCenter.default.removeObserver(vc) // 清理TableVC的数据源/代理 if let tableVC = vc as? UITableViewController { tableVC.tableView.dataSource = nil tableVC.tableView.delegate = nil } // 清理SplitVC的代理 if let splitVC = vc as? UISplitViewController { splitVC.delegate = nil // 如果有自定义代理也一并置空 if let customSplitVC = splitVC as? YourCustomSplitVC { customSplitVC.customDelegate = nil } } // 递归处理子控制器 vc.children.forEach(cleanup) } cleanup(oldRootVC) }
方案B:用unwind segue配合根VC重置
如果一定要保留unwind,就在登录VC的unwind方法里执行根VC重置,确保unwind完成后再替换:
// 在LoginViewController里定义unwind入口 @IBAction func unwindToLogin(_ segue: UIStoryboardSegue) { // 这里执行根VC重置 let mainStoryboard = UIStoryboard(name: "Main", bundle: nil) let newLoginVC = mainStoryboard.instantiateViewController(withIdentifier: "LoginVC") UIApplication.shared.windows.first?.rootViewController = newLoginVC }
然后在Storyboard里给登出按钮关联这个unwind segue,这样系统会先完成unwind的视图栈销毁,再执行根VC替换,减少冲突。
2. 检查所有代理的引用修饰符
确保SplitVC、表格VC的代理都用weak修饰,避免强引用导致旧VC无法释放:
// 示例:表格VC的自定义代理 protocol MasterTableDelegate: AnyObject {} // 必须继承AnyObject才能用weak class MasterTableViewController: UITableViewController { weak var delegate: MasterTableDelegate? // 用weak修饰 } // SplitVC的代理(系统的UISplitViewControllerDelegate本身就是weak属性,但自定义代理要注意) class YourCustomSplitVC: UISplitViewController { weak var customDelegate: YourSplitDelegate? // 自定义代理加weak }
3. 清理后台任务和网络请求
登出时取消所有未完成的网络请求、定时器等,避免回调时访问已释放的VC:
// 在你的表格VC或SplitVC里,添加清理方法 func cleanupOnLogout() { // 取消网络请求 yourNetworkTask?.cancel() yourNetworkTask = nil // 销毁定时器 yourTimer?.invalidate() yourTimer = nil }
然后在登出的清理逻辑里调用这些方法。
总结
核心就是避免视图栈操作冲突和彻底清理内存引用,按照上面的步骤排查,应该能解决SplitVC的EXC_BAD_ACCESS崩溃问题。
内容的提问来源于stack exchange,提问作者Ephuvex
相关产品推荐
相关产品推荐

