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
相关产品推荐
相关产品推荐

