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

变量值变更时如何更新Timer定时器中的函数调用

问题根因

你遇到的问题基本都是以下原因导致的:

  1. 没有处理Timer的循环引用,导致旧实例无法被释放,旧Timer持续在后台用旧url发请求,你看到的旧值请求实际是已经被替换掉的旧实例发出来的
  2. 如果你的网络逻辑写在值类型(struct)中,Timer闭包会在创建时捕获值类型的不可变快照,后续原实例的属性变更不会同步到Timer持有的快照中,永远使用创建时的url值
  3. 没有持有Timer的引用,无法在url变更或者实例销毁时停掉旧Timer,可能出现多个Timer同时运行的情况
修复方案

按以下步骤修改代码即可:

  • 首先确保承载Timer和网络请求逻辑的类型是引用类型(class),不要用struct写定时轮询逻辑。如果业务必须用值类型,把相关逻辑抽到一个独立的NSObject子类中持有。
  • 给你的类增加一个可选属性持有Timer实例,避免重复创建,也方便后续销毁:
private var pollingTimer: Timer?
  • 修改connect()方法,创建新Timer前先销毁已存在的旧Timer,同时给闭包加弱引用避免循环引用,可选把Timer加入common运行循环模式,避免滑动UIScrollView时Timer被暂停:
func connect() {
    // 销毁已有旧定时器,避免重复轮询
    pollingTimer?.invalidate()
    
    self.receive()
    // 创建新定时器
    pollingTimer = Timer.scheduledTimer(withTimeInterval: self.frequency, repeats: true) { [weak self] _ in
        self?.receive()
    }
    // 加入通用运行循环模式,保证交互时定时器不暂停
    if let timer = pollingTimer {
        RunLoop.main.add(timer, forMode: .common)
    }
}
  • 给url属性加didSet观察者,url变更时按需触发更新:因为Timer每次触发都会实时读取当前self.url的最新值,正常情况下不需要重建Timer,如果你需要url变更后立刻拉取一次数据,直接调用receive()即可;如果业务要求url变更后轮询间隔重新计时,再调用connect()重建定时器:
var url: URL? {
    didSet {
        // url变更后立即拉取一次最新数据
        receive()
        // 若需要重置轮询计时,取消下面这行的注释
        // connect()
    }
}
  • 实现deinit方法,实例销毁时自动停掉定时器,避免内存泄漏和后台无效请求:
deinit {
    pollingTimer?.invalidate()
}
关键说明

90%以上的“Timer持有旧值”问题都是循环引用导致的:Timer会强引用传入的闭包,闭包如果直接强持有self,而self又强持有Timer,三者会形成无法打破的引用环,实例永远不会被释放。这时候你就算给新的实例设置了新url,旧实例的Timer还会一直运行,持续用旧url发请求,看起来就像Timer永远拿不到新url一样。

内容的提问来源于stack exchange,提问作者Alexander Görtzen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 05:30:47