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

Swift中Backoff timer搭配URLRequest使用触发延迟问题如何修复

问题根因

你遇到的异常是时序错位导致的:

  • 同步代码场景下,你在定时器回调block内直接同步修改了isFailing的值,executeBackoffInterval方法执行完block后计算下一次间隔时,已经能读到修改后的isFailing状态,所以退避逻辑运行正常。
  • 搭配URLSession异步请求时,block执行后只是发起了网络请求,executeBackoffInterval会立刻计算下一次间隔,此时网络请求还未返回,isFailing还保持初始的false值,所以第一次计算出的间隔直接用了defaultInterval也就是60秒,这就是你看到首次间隔异常长的原因。等第一次网络请求回调执行把isFailing改成true后,后续的间隔计算就符合预期了。

修复方案

把下一次定时器的调度逻辑从executeBackoffInterval里拆分出来,改成在用户确认请求结果、更新isFailing状态后再触发,确保计算间隔时已经拿到最新的执行结果状态。改动最小的可行实现如下:

import Foundation

public final class BackoffTimer: Timer {
    static let defaultInterval: TimeInterval = 60
    static let minimumInterval: TimeInterval = 1
    
    private var isFinished = false
    private var isFirstRun = false
    public var isFailing = false {
        didSet { 
            isFirstRun = oldValue != isFailing
            // 状态更新后再调度下一次执行
            scheduleNextRun()
        }
    }
    
    private var interval: TimeInterval = 0.0
    override public var timeInterval: TimeInterval {
        get { interval }
        set { interval = newValue }
    }
    
    private var backoffTimer: Timer?
    private var backoffBlock: ((_ timer: BackoffTimer) -> Void)?
    
    public convenience init(_ block: @escaping (_ timer: BackoffTimer) -> Void) {
        self.init()
        backoffBlock = block
        // 首次直接执行,不需要等待初始间隔,符合常规重试逻辑
        executeBackoffInterval(nil)
        // 如果需要保留原逻辑首次等待1秒,替换上面的直接调用为下面这句即可
        // backoffTimer = Timer.scheduledTimer(timeInterval: Self.minimumInterval, target: self, selector: #selector(executeBackoffInterval(_:)), userInfo: nil, repeats: false)
    }
    
    @objc private func executeBackoffInterval(_ timer: Timer?) {
        backoffBlock?(self)
    }
    
    private func scheduleNextRun() {
        guard !isFinished else {
            backoffTimer?.invalidate()
            return
        }
        if isFailing {
            interval = !isFirstRun ? timeInterval * 2 : Self.minimumInterval
            interval = min(timeInterval, Self.defaultInterval)
        } else {
            interval = Self.defaultInterval
        }
        isFirstRun = false
        backoffTimer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(executeBackoffInterval(_:)), userInfo: nil, repeats: false)
    }
    
    // 新增主动终止方法,避免内存泄漏
    public func stop() {
        isFinished = true
        backoffTimer?.invalidate()
    }
}

额外提示:Timer默认会被添加到主线程的RunLoop中,如果你的定时器需要在子线程运行,需要手动将Timer添加到对应子线程的RunLoop里,避免定时器不触发的问题。

内容的提问来源于stack exchange,提问作者Bram

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 15:45:03