Coordinator模式实现中闭包引发的循环引用问题排查
解决Coordinator模式中闭包引发的循环引用问题
你遇到的核心问题是子Coordinator和它的deinitIfNeeded闭包形成了循环引用,导致内存无法正常释放。咱们一步步拆解问题并给出解决方案:
循环引用的根源
看你这段代码:
coord.deinitIfNeeded = { [weak self] in guard let self = self else { return } self.free(coord) }
虽然你对self用了weak修饰,但闭包同时强引用捕获了coord这个子Coordinator实例。这就形成了一个无法打破的闭环:
SecondViewCoordinator实例(coord)持有deinitIfNeeded闭包- 闭包持有
coord的强引用 - 两者互相依赖,即使你从
childCoordinators里移除了coord,GC也没法回收任何一方
解决思路与代码修改
方案1:对闭包中的coord也使用弱引用
直接在闭包的捕获列表里给coord加上weak,打破循环引用链:
func goTo() { let coord = SecondViewCoordinator(nav: navigationController) add(coord) coord.start() // 在捕获列表同时添加weak self和weak coord coord.deinitIfNeeded = { [weak self, weak coord] in guard let self = self, let validCoord = coord else { return } self.free(validCoord) } }
这样闭包不再强持有coord,当childCoordinators移除coord后,只要没有其他强引用,coord就能正常释放,闭包也会随之销毁。
方案2:优化Coordinator的通知逻辑(更优雅的设计)
可以给每个Coordinator添加唯一标识,让父Coordinator通过ID匹配移除子Coordinator,彻底避免捕获实例:
// 修改Coordinator基类,添加唯一ID属性 class Coordinator { weak var navigationController: UINavigationController? var childCoordinators: [Coordinator] = [] var deinitIfNeeded: (() -> ())? let identifier = UUID() // 新增唯一标识 init(nav: UINavigationController?) { self.navigationController = nav } func add(_ coordinator: Coordinator) { childCoordinators.append(coordinator) } // 修改free方法,通过ID匹配移除 func free(withIdentifier id: UUID) { childCoordinators = childCoordinators.filter({ $0.identifier != id }) } }
然后在设置闭包时,只传递子Coordinator的ID:
func goTo() { let coord = SecondViewCoordinator(nav: navigationController) add(coord) coord.start() let coordID = coord.identifier coord.deinitIfNeeded = { [weak self] in guard let self = self else { return } self.free(withIdentifier: coordID) } }
这种方式从根源上杜绝了闭包捕获子Coordinator实例的可能,彻底避免循环引用。
验证是否解决问题
建议在Coordinator类里添加deinit打印,方便确认实例是否正常释放:
deinit { print("✅ \(type(of: self)) 已释放,ID: \(identifier)") }
运行代码后,当控制器弹出并触发deinitIfNeeded后,如果控制台能看到打印,说明内存释放正常。
内容的提问来源于stack exchange,提问作者PiterPan
相关产品推荐
相关产品推荐

