在Timer.scheduledTimer重复代码中调用异步函数如何避免内存泄漏?
解决SDK重复触发下载完成闭包的问题
问题背景
使用第三方SDK发起下载任务,为处理下载未完成时应用重启/关闭后的错误,实现了Timer重试逻辑:下载失败后每隔5秒调用一次sdkFunction重试。但实际运行中,下载完成后会一次性多次打印"download complete",次数与Timer触发的重试次数完全一致。
已知SDK行为:
- 调用
sdkFunction时才会检查下载状态,闭包不会异步执行,除非再次调用该函数 - 下载完成后,再次调用
sdkFunction会触发所有之前调用时传入的闭包 - 无法修改SDK源码
原因分析
这不是你代码的引用捕获问题,而是SDK的设计特性:它会持久化保存所有调用sdkFunction时传入的闭包实例,直到下载任务完成后,一次性触发所有留存的闭包。每次Timer触发调用sdkFunction,都会新增一个闭包被SDK持有,最终导致下载完成时批量执行所有闭包。
解决方案
核心思路是让旧的重试闭包失效,即使被SDK触发也不执行后续的完成逻辑。以下是两种可行实现方式:
方式1:用有效性标志位过滤旧闭包
在类中添加标志位,每次发起新重试时标记旧闭包无效,闭包执行时先检查有效性:
// 类成员变量 private var isCurrentCompletionHandlerValid = false private weak var timer: Timer? // 重试逻辑代码 DispatchQueue.main.async { // 先取消已有Timer,避免重复创建 self.timer?.invalidate() self.timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { [weak self] _ in guard let self = self else { return } print("Timer fired", Date()) // 标记之前的闭包失效 self.isCurrentCompletionHandlerValid = false // 标记当前闭包为有效 self.isCurrentCompletionHandlerValid = true self.sdkFunction() { [weak self] error in guard let self = self else { return } // 无效闭包直接返回,不执行后续逻辑 guard self.isCurrentCompletionHandlerValid else { return } if let error = error { print("some error: \(error)") return } print("download complete", Date()) self.timer?.invalidate() } } }
方式2:用版本号过滤旧闭包
用递增的版本号标记每次请求,闭包仅处理最新版本的请求,更适合复杂场景:
// 类成员变量 private var latestRequestVersion = 0 private weak var timer: Timer? // 重试逻辑代码 DispatchQueue.main.async { self.timer?.invalidate() self.timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { [weak self] _ in guard let self = self else { return } print("Timer fired", Date()) // 生成当前请求的版本号 let currentVersion = self.latestRequestVersion + 1 self.latestRequestVersion = currentVersion self.sdkFunction() { [weak self] error in guard let self = self else { return } // 仅处理最新版本的请求,旧版本直接忽略 guard self.latestRequestVersion == currentVersion else { return } if let error = error { print("some error: \(error)") return } print("download complete", Date()) self.timer?.invalidate() } } }
额外注意
添加[weak self]捕获列表是为了避免Timer、闭包与当前类实例形成循环引用,防止内存泄漏,这是必要的优化。
内容的提问来源于stack exchange,提问作者ADC2000
相关产品推荐
相关产品推荐

