Swift Combine:借助Coordinator模式与订阅管理避免内存泄漏
用Combine替代Coordinator模式代理时的内存泄漏问题解决方法
问题描述
我在实现Coordinator模式时尝试用Combine替代代理,但从childCoordinators列表移除协调器后出现了内存泄漏。如何确保协调器能自动释放?具体来说,我希望coordinator.actions.sink订阅能被正确终止,协调器能从内存中被清理。
相关代码:
// MARK: - BaseCoordinator @MainActor class BaseCoordinator: Coordinator { let navigator: NavigatorType var childCoordinators: [Coordinator] = [] var cancellables = Set<AnyCancellable>() init(navigator: NavigatorType) { self.navigator = navigator } func start() { // Default implementation, can be overridden by subclasses } } // MARK: - ExampleFlowCoordinator final class ExampleFlowCoordinator: BaseCoordinator { override func start() { let coordinator = ExampleCoordinator() add(coordinator) coordinator.actions.sink { [weak self] action in guard let self else { return } switch action { case .done: remove(coordinator) } } .store(in: &cancellables) navigator.setRootViewController(coordinator.rootViewController, animated: true) coordinator.start() } private func showExample2() { let coordinator = ExampleCoordinator2() coordinator.actions.sink { [weak self] action in guard let self else { return } switch action { case .done: remove(coordinator) } } .store(in: &cancellables) add(coordinator) coordinator.start() navigator.present(coordinator.rootViewController, animated: true) { [weak self] in guard let self else { return } remove(coordinator) } } }
补充:在showExample2()函数中,执行remove(coordinator)后,协调器已成功从childCoordinators中移除,但由于coordinator.actions.sink的存在,协调器仍驻留在内存中(移除该订阅代码后,内存释放正常)。
核心问题分析
内存泄漏的根源是父协调器的cancellables集合持有了订阅,而订阅又捕获了子协调器的强引用——即使从childCoordinators移除子协调器,订阅的强引用依然让子协调器无法被ARC回收。
解决方法
方法1:让子协调器自行管理订阅
将订阅生命周期与子协调器绑定,当子协调器被释放时,订阅自动取消。
修改子协调器(以ExampleCoordinator2为例):
final class ExampleCoordinator2: BaseCoordinator { let actions = PassthroughSubject<Example2Action, Never>() private var ownCancellables = Set<AnyCancellable>() // 子协调器专属的订阅集合 // 对外暴露订阅方法,将订阅存储到自己的集合中 func subscribeToActions(_ handler: @escaping (Example2Action) -> Void) { actions.sink(receiveValue: handler) .store(in: &ownCancellables) } }
父协调器调用方式调整:
private func showExample2() { let coordinator = ExampleCoordinator2() // 调用子协调器的订阅方法 coordinator.subscribeToActions { [weak self] action in guard let self else { return } switch action { case .done: remove(coordinator) // 无需手动取消订阅,子协调器释放时会自动清理ownCancellables } } add(coordinator) coordinator.start() navigator.present(coordinator.rootViewController, animated: true) { [weak self] in guard let self else { return } remove(coordinator) } }
方法2:父协调器关联订阅与子协调器
在父协调器中用字典存储子协调器对应的订阅,移除子协调器时同步取消订阅。
修改BaseCoordinator:
@MainActor class BaseCoordinator: Coordinator { let navigator: NavigatorType var childCoordinators: [Coordinator] = [] private var coordinatorSubscriptions: [Coordinator: AnyCancellable] = [:] // 新增:存储订阅与子协调器的映射 init(navigator: NavigatorType) { self.navigator = navigator } func start() { // Default implementation, can be overridden by subclasses } // 新增:存储子协调器的订阅 func storeSubscription(for coordinator: Coordinator, subscription: AnyCancellable) { coordinatorSubscriptions[coordinator] = subscription } // 重写remove方法,移除时同步取消订阅 func remove(_ coordinator: Coordinator) { childCoordinators.removeAll { $0 === coordinator } coordinatorSubscriptions.removeValue(forKey: coordinator) // 取消订阅,释放强引用 } }
父协调器showExample2()调整:
private func showExample2() { let coordinator = ExampleCoordinator2() // 创建订阅并存储到映射字典 let subscription = coordinator.actions.sink { [weak self] action in guard let self else { return } switch action { case .done: remove(coordinator) } } storeSubscription(for: coordinator, subscription: subscription) add(coordinator) coordinator.start() navigator.present(coordinator.rootViewController, animated: true) { [weak self] in guard let self else { return } remove(coordinator) } }
方法3:利用Combine自动取消的特性
如果actions事件可以直接绑定到父协调器的方法,可结合弱引用使用sink,配合方法2的映射存储,确保订阅能随子协调器移除而取消。
// 父协调器新增事件处理方法 private func handleExample2Action(_ action: Example2Action, coordinator: ExampleCoordinator2) { switch action { case .done: remove(coordinator) } } // 订阅时的写法 private func showExample2() { let coordinator = ExampleCoordinator2() let subscription = coordinator.actions .sink { [weak self] action in self?.handleExample2Action(action, coordinator: coordinator) } storeSubscription(for: coordinator, subscription: subscription) add(coordinator) coordinator.start() navigator.present(coordinator.rootViewController, animated: true) { [weak self] in guard let self else { return } remove(coordinator) } }
内容的提问来源于stack exchange,提问作者Shape Fit
相关产品推荐
相关产品推荐

