如何在多层子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

