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

