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

Swift闭包弱引用捕获仍导致VIP架构中Interactor内存泄漏问题排查

问题拆解与解决方案

咱们来一步步分析你遇到的崩溃和内存泄漏问题,找到核心原因并给出可行的解决办法:

核心问题出在哪?

你碰到的崩溃(强制解包view!)和Interactor无法释放的问题,本质是你的响应式属性没有提供取消订阅的机制:

  • someSharedService是单例,会一直存活在内存中,它的SubscribableProperty里的subscribers数组会强引用所有订阅过的闭包。
  • 虽然你在闭包中用了[weak self],但因为没有取消订阅的逻辑,这个闭包会一直留在subscribers数组里。如果Interactor因为这个闭包的存在无法被正确释放(你的内存图显示闭包是Interactor的唯一持有者,这说明闭包虽然用了弱引用,但可能因为订阅未取消导致Interactor无法被回收),当someSharedProperty更新时,闭包会触发presenter.sendUpdates(),而此时UIViewController已经被dismiss,Presenter的view是nil,强制解包就直接崩溃了。

怎么解决?

我们需要给响应式属性添加取消订阅的能力,同时修复Presenter中的强制解包问题,确保整个链条能正确释放:

1. 给Subscribable协议增加取消订阅的功能

首先修改协议,用唯一标识符来管理订阅,方便后续移除:

protocol Subscribable {
    associatedtype SubscribeType
    typealias UpdateResultsHandler = (_ values: SubscribeType) -> Void
    // 用UUID作为订阅的唯一标识
    typealias SubscriptionToken = UUID
    
    var subscribers: [SubscriptionToken: UpdateResultsHandler] { get set }
    
    func subscribeForUpdate(_ updateHandler: @escaping UpdateResultsHandler) -> SubscriptionToken
    func cancelSubscription(with token: SubscriptionToken)
}

2. 更新SubscribableProperty的实现

把原来的数组改成字典存储订阅者,这样可以通过token精准移除对应的闭包:

class SubscribableProperty<Type>: Subscribable {
    typealias SubscribeType = Type
    var subscribers: [SubscriptionToken: UpdateResultsHandler] = [:]
    var value: Type {
        didSet {
            subscribers.values.forEach { $0(value) }
        }
    }
    
    init(value: Type) {
        self.value = value
    }
    
    func subscribeForUpdate(_ updateHandler: @escaping UpdateResultsHandler) -> SubscriptionToken {
        let token = UUID()
        subscribers[token] = updateHandler
        return token
    }
    
    func cancelSubscription(with token: SubscriptionToken) {
        subscribers.removeValue(forKey: token)
    }
}

3. 在Interactor中管理订阅的生命周期

在Interactor里保存订阅的token,并在Interactor被释放时取消订阅,这样就能把闭包从someSharedService的订阅列表中移除:

class YourInteractor {
    private var subscriptionToken: SubscribableProperty<YourType>.SubscriptionToken?
    
    init() {
        // 订阅时保存返回的token
        subscriptionToken = someSharedService.someSharedProperty.subscribeForUpdate { [weak self] _ in
            self?.presenter.sendUpdates()
        }
    }
    
    deinit {
        // Interactor被释放时,取消订阅
        if let token = subscriptionToken {
            someSharedService.someSharedProperty.cancelSubscription(with: token)
        }
    }
}

4. 修复Presenter中的强制解包

不管内存问题有没有解决,强制解包都是危险操作,改成可选绑定更安全:

func sendUpdates() {
    guard let view = view else {
        // 可以加个日志,方便调试时知道view已经不存在了
        print("View is nil, skipping render")
        return
    }
    view.render()
}

最后再确认下VIP架构的引用关系

你的VIP引用链是正确的:

  • UIViewController 强引用 Interactor
  • Interactor 强引用 Presenter
  • Presenter 弱引用 UIViewController

只要UIViewController被dismiss后,它的引用被释放,Interactor就会触发deinit取消订阅,闭包被移除,整个链条就能被正确回收,不会再出现内存泄漏和崩溃的问题。

内容的提问来源于stack exchange,提问作者Alex Sh.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.01 02:37:35