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强引用InteractorInteractor强引用PresenterPresenter弱引用UIViewController
只要UIViewController被dismiss后,它的引用被释放,Interactor就会触发deinit取消订阅,闭包被移除,整个链条就能被正确回收,不会再出现内存泄漏和崩溃的问题。
内容的提问来源于stack exchange,提问作者Alex Sh.
相关产品推荐
相关产品推荐

