You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在多层子Coordinator架构中检测导航返回并避免内存泄漏

问题分析与解决方案

首先,咱们先拆解你遇到的两个核心问题:导航代理不响应、内存泄漏,然后再聊架构设计的疑问。

核心问题根源

1. 导航代理冲突

UINavigationController的delegate是唯一的——你在HomeCoordinator的start()方法里已经把自己设为导航控制器的代理了,后续AddWorkoutCoordinator就算实现了UINavigationControllerDelegate,也没机会成为代理,所以它的didShow方法根本不会被触发。

2. 循环引用导致内存泄漏

你代码里的AddWorkoutCoordinator的parentCoordinator是强引用,而HomeCoordinator的childCoordinators数组又强引用了AddWorkoutCoordinator,这就形成了循环引用:HomeCoordinator → AddWorkoutCoordinator → HomeCoordinator,导致两者都无法被释放。同理,AddExerciseCoordinator如果没有用弱引用关联父协调器,也会有同样问题。


分步解决方案

第一步:打破循环引用

把所有Coordinator的parentCoordinator声明为weak,因为父协调器的生命周期肯定长于子协调器,子协调器不需要强引用父:

// 给Coordinator协议统一添加weak的parentCoordinator
protocol Coordinator: AnyObject {
    var childCoordinators: [Coordinator] { get set }
    var navigationController: UINavigationController { get set }
    weak var parentCoordinator: Coordinator? { get set } // 新增weak修饰
    func start()
    func childDidFinish(_ child: Coordinator?)
}

// 然后在各个Coordinator类里遵循这个协议即可

第二步:统一由根协调器处理导航代理

不要让每个子协调器去抢导航代理,让HomeCoordinator作为唯一的导航代理,在它的didShow方法里统一处理所有弹出事件,同时通知对应的父协调器移除子协调器。

修改HomeCoordinator的导航代理方法:

func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
    guard let fromViewController = navigationController.transitionCoordinator?.viewController(forKey: .from) else { return }
    // 如果fromVC还在导航栈里(比如是pop到中间页面或modal dismiss),跳过处理
    if navigationController.viewControllers.contains(fromViewController) { return }
    
    // 先处理从Exercise VC弹出的情况
    if let exerciseVC = fromViewController as? NewExerciseViewController {
        guard let exerciseCoordinator = exerciseVC.presenter?.exerciseCoordinatorDelegate else { return }
        // 找到Exercise协调器的父(Workout协调器),通知它移除子协调器
        if let workoutCoordinator = exerciseCoordinator.parentCoordinator as? AddWorkoutCoordinator {
            workoutCoordinator.childDidFinish(exerciseCoordinator)
        }
    }
    // 再处理从Workout VC弹出的情况
    else if let workoutVC = fromViewController as? NewWorkoutViewController {
        guard let workoutCoordinator = workoutVC.presenter?.workoutCoordinatorDelegate else { return }
        childDidFinish(workoutCoordinator)
    }
}

同时,给AddExerciseCoordinator补上parentCoordinator的设置:

// 在AddWorkoutCoordinator跳转Exercise的方法里
extension AddWorkoutCoordinator: AddWorkoutCoordinatorDelegate {
    func goToAddExercise() {
        let child = AddExerciseCoordinator(navigationController: navigationController)
        child.passExerciseToWorkoutDelegate = self
        child.parentCoordinator = self // 设置父协调器
        childCoordinators.append(child)
        child.start()
    }
}

第三步:优化子协调器移除逻辑

给Coordinator协议添加默认实现,避免每个协调器重复写移除代码:

extension Coordinator {
    func childDidFinish(_ child: Coordinator?) {
        guard let child = child else { return }
        childCoordinators.removeAll { $0 === child }
    }
}

关于架构设计的疑问

你的初始架构是完全正确的!应该把AddExerciseCoordinator作为AddWorkoutCoordinator的子协调器,这完全符合单一职责原则:

  • HomeCoordinator只负责首页相关的导航和子协调器管理
  • AddWorkoutCoordinator专注于 workout 创建流程的导航,以及自己的子协调器(Exercise)管理
  • AddExerciseCoordinator只处理 exercise 创建的逻辑

这种层级划分让每个模块职责清晰,代码解耦,后续扩展或修改某一流程时,不会影响其他模块。之前的问题只是导航代理和内存引用的实现细节问题,和架构设计无关。

内容的提问来源于stack exchange,提问作者TeddyKen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 10:57:37