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

Timer失效后仍持续触发求助:AFK检测计时器异常排查

我来帮你排查这个Timer调用invalidate()后还持续触发的问题——从你的代码和控制台输出来看,大概率是多个Timer实例同时在运行导致的,下面给你拆解原因和修复方案:

问题分析

从控制台重复输出Round time is up!和AFK提示可以看出,updateRoundTimer被多个Timer实例反复触发。核心原因主要有两点:

  • 你多次调用startRoundTimer()创建新Timer,但没有先停止之前运行的Timer,导致旧Timer的引用被新Timer覆盖,但旧Timer仍然被RunLoop持有,持续触发回调。
  • 若invalidate()的调用线程和Timer创建线程不一致,也可能导致Timer无法被正确停止。
修复步骤

1. 创建新Timer前先停止旧Timer

修改startRoundTimer()函数,在创建新Timer前先检查并停止已有的Timer,避免多个实例共存:

func startRoundTimer() {
    // 先停止之前的Timer(如果存在)
    timeRemaining?.invalidate()
    timeRemaining = nil
    
    // 创建新Timer
    timeRemaining = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(self.updateRoundTimer), userInfo: nil, repeats: true)
    timerActivity.startAnimating()
}

2. 在回调中彻底停止Timer并清空引用

在updateRoundTimer触发结束逻辑时,不仅调用invalidate(),还要把timeRemaining置为nil,确保没有残留引用:

@objc func updateRoundTimer() {
    if roundTime < 0.5 {
        print ("Round time is up!")
        // 停止Timer并清空引用
        timeRemaining?.invalidate()
        timeRemaining = nil
        
        skipTurnDueToInactivity()
        timerActivity.stopAnimating()
        skipTurn = true
    } else {
        roundTime -= 0.5
    }
    timerLabel.text = String(format: "%.01f", roundTime)
}

3. 确保invalidate()在主线程调用

Timer的invalidate()必须在创建它的线程(通常是主线程)执行,如果skipTurnDueToInactivity可能在后台线程被调用,要切换到主线程操作Timer:

func skipTurnDueToInactivity() {
    // 切换到主线程停止Timer
    DispatchQueue.main.async { [weak self] in
        self?.timeRemaining?.invalidate()
        self?.timeRemaining = nil
    }
    
    print ("YOU HAVE BEEN AFK LONG ENOUGH! SKIPPING TURN! \(afkSkips)")
    if afkSkips == 3 {
        print ("YOU HAVE BEEN AFK 3 TIMES, DISCONNECTING YOU FROM MATCH DUE TO INACTIVITY")
        print("You ded son!")
        sendData(key: DID_ELIMINATE, stage: currentStage, int: nil, double: nil, player: nil, notes: nil)
        didEliminatePlayer(playerDed: GKLocalPlayer.localPlayer())
    } else {
        afkSkips += 1
    }
}

4. 重置回合时同步停止Timer

修改resetRoundtimer(),重置时间的同时停止当前运行的Timer,避免旧Timer继续触发:

func resetRoundtimer() {
    roundTime = 75
    // 重置时停止Timer
    timeRemaining?.invalidate()
    timeRemaining = nil
}
补充说明

iOS的Timer被RunLoop持有后,即使你把变量引用置为nil,只要没有调用invalidate(),RunLoop仍然会保留Timer并持续触发回调。所以必须确保每个创建的Timer都被正确调用invalidate(),并且在创建新Timer前清理旧实例,才能彻底避免重复触发的问题。

内容的提问来源于stack exchange,提问作者dvd.Void

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:31:14