Swift iOS应用复用缓存ViewController时跳转崩溃问题求助
嘿,这个问题我之前也踩过坑!你想复用ViewController实例的思路没问题,但直接让两个VC互相保存强引用会触发循环引用——iOS里ViewController的属性默认是强引用,一旦VC A强持有VC B、VC B又强持有VC A,ARC会认为这俩对象永远被需要,根本不会释放,最终导致内存泄漏,甚至引发各种奇怪的功能异常。
下面给你几个靠谱的解决方案,按推荐程度排序:
如果你的页面是基于导航栈的,这是最简单的解决方式:
- 从VC A跳转到VC B时,用
pushViewController:animated:,此时VC A会被保存在导航栈里,不会被释放。 - 当VC B需要切换回VC A时,绝对不要新建VC A实例,直接调用:
// Objective-C [self.navigationController popViewControllerAnimated:YES];
// Swift self.navigationController?.popViewController(animated: true)
这样就会回到原来的VC A实例,完全复用之前的状态。
如果你的需求是从VC B再次“展示”VC A(不是单纯返回),可以先检查导航栈里是否已有VC A实例,有的话直接跳转到它:
// VC B的按钮点击方法 if let targetVC = navigationController?.viewControllers.first(where: { $0 is VCA }) { navigationController?.popToViewController(targetVC, animated: true) } else { // 极端情况:栈里没有VC A,再创建新实例 let newVC = VCA() navigationController?.pushViewController(newVC, animated: true) }
方案2:用容器ViewController统一管理(适合自定义切换逻辑)
如果你的页面切换不是基于导航栈,或者需要更灵活的自定义动画,建议用一个父容器VC来持有两个子VC的实例,负责切换显示:
// 容器VC class ContainerViewController: UIViewController { private var vcA: VCA! private var vcB: VCB! override func viewDidLoad() { super.viewDidLoad() // 初始化两个VC实例(只创建一次) vcA = VCA() vcB = VCB() // 给子VC设置切换回调(用weak避免循环引用) vcA.switchToVCBHandler = { [weak self] in self?.showVCB() } vcB.switchToVCAHandler = { [weak self] in self?.showVCA() } // 默认显示VC A addChild(vcA) vcA.view.frame = view.bounds view.addSubview(vcA.view) vcA.didMove(toParent: self) } private func showVCB() { // 移除VC A vcA.willMove(toParent: nil) vcA.view.removeFromSuperview() vcA.removeFromParent() // 添加VC B addChild(vcB) vcB.view.frame = view.bounds view.addSubview(vcB.view) vcB.didMove(toParent: self) } private func showVCA() { // 移除VC B vcB.willMove(toParent: nil) vcB.view.removeFromSuperview() vcB.removeFromParent() // 添加VC A addChild(vcA) vcA.view.frame = view.bounds view.addSubview(vcA.view) vcA.didMove(toParent: self) } } // VC A代码 class VCA: UIViewController { // 定义切换回调闭包 var switchToVCBHandler: (() -> Void)? @IBAction func switchButtonTapped(_ sender: UIButton) { switchToVCBHandler?() } } // VC B代码 class VCB: UIViewController { var switchToVCAHandler: (() -> Void)? @IBAction func switchButtonTapped(_ sender: UIButton) { switchToVCAHandler?() } }
这种方式下,只有容器VC持有两个子VC的强引用,子VC通过弱引用的闭包通知容器切换,完全避免了循环引用,同时保证两个VC实例只会被创建一次。
方案3:用弱引用避免循环引用(不推荐)
如果你一定要让两个VC互相持有,必须把其中一个引用改成弱引用(weak),打破循环:
// VC A class VCA: UIViewController { weak var vcB: VCB? // 弱引用 @IBAction func switchToVCBTapped(_ sender: UIButton) { if let existingVCB = vcB { present(existingVCB, animated: true) } else { let newVCB = VCB() newVCB.vcA = self // VCB里的vcA是强引用 vcB = newVCB present(newVCB, animated: true) } } } // VC B class VCB: UIViewController { var vcA: VCA? // 强引用 @IBAction func switchToVCATapped(_ sender: UIButton) { // 注意:这里应该dismiss回到VC A,而不是present新的 dismiss(animated: true) } }
但这种方式风险很高,比如如果VC A被释放,VC B里的vcA会变成nil,而且用present来回切换会导致视图栈越来越深,非常不推荐在实际项目中使用。
内容的提问来源于stack exchange,提问作者Math
相关产品推荐
相关产品推荐

