如何解决Combine中AsyncPublisher的内存泄漏问题
异步遍历AsyncPublisher引发内存泄漏的问题及解决
问题描述
使用AsyncPublisher(异步序列)遍历定时器序列时,即便在Task中通过[weak self]弱引用捕获实例,将类对象置为nil后,实例仍无法被释放,定时器持续输出时间戳,存在内存泄漏。
相关代码如下:
import Foundation import Combine class MyServiceClass { let timerSequence = Timer.publish(every: 1, on: .main, in: .default).autoconnect().values init() { Task { [weak self] in await self?.setUpStreamIteration() } } func setUpStreamIteration() async { for await time in timerSequence { print(time) } } } var service: MyServiceClass? = MyServiceClass() service = nil
运行后控制台持续输出时间戳,说明MyServiceClass实例未被正常释放。
问题原因
核心问题在于**for await...in遍历异步序列时会强引用序列本身**:
timerSequence是MyServiceClass的实例属性,类实例持有该序列;- 遍历操作强引用
timerSequence,而序列因定时器持续运行保持活跃,间接“留住”了类实例,形成隐性循环引用; - 虽然
Task用[weak self]捕获,但进入setUpStreamIteration方法后,遍历对序列的强引用已形成,弱引用无法打破这个循环。
解决方案
方案1:保存遍历Task并在deinit中取消
通过保存用于遍历的Task实例,在类的deinit方法中取消该Task,终止遍历操作,释放对序列的引用,打破循环:
import Foundation import Combine class MyServiceClass { let timerSequence = Timer.publish(every: 1, on: .main, in: .default).autoconnect().values private var iterationTask: Task<Void, Never>? init() { iterationTask = Task { [weak self] in await self?.setUpStreamIteration() } } func setUpStreamIteration() async { // Task被取消时,遍历循环会自动终止 for await time in timerSequence { print(time) } } deinit { iterationTask?.cancel() print("MyServiceClass 已释放") } } var service: MyServiceClass? = MyServiceClass() service = nil
方案2:在遍历中显式处理Task取消(可选)
可以在遍历方法中捕获Task取消错误,让逻辑更清晰:
func setUpStreamIteration() async { do { // Task被取消时会抛出CancellationError for try await time in timerSequence.values { print(time) } } catch is CancellationError { print("遍历已取消") } catch { print("遍历出错:\(error)") } }
验证效果
修改后将service置为nil,控制台会输出MyServiceClass 已释放,且时间戳停止输出,说明实例成功释放,内存泄漏问题解决。
内容的提问来源于stack exchange,提问作者SPatel
相关产品推荐
相关产品推荐

