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

在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 14:45:29