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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:02:18