Swift:后台线程创建的Scheduled Timer到期后未触发
问题原因与解决方案
核心问题
你在后台线程创建了Timer,但后台线程的RunLoop默认处于未启动状态——即便你把Timer加到了RunLoop里,没有启动的RunLoop根本不会处理Timer的触发事件,这就是闭包没执行的核心原因。
另外,Timer.scheduledTimer()方法本身已经会把创建好的Timer自动添加到当前RunLoop的.default模式下,你后面额外调用RunLoop.current.add(timer, forMode: .common)属于冗余操作,解决不了根本问题。
两种可行解决方案
方案一:切换到主线程创建Timer(推荐)
主线程的RunLoop是系统自动维护并持续运行的,把Timer的创建逻辑放到主线程,就能保证Timer到期后正常触发闭包:private func expireToken(afterTimeInterval timeInterval: TimeInterval) { print("[DEBUG] Schedule expiration, time: \(timeInterval), main thread \(Thread.isMainThread)") DispatchQueue.main.async { let timer = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false) { timer in print("[DEBUG] Expiring token") self.tokenContainer = .expired timer.invalidate() } } }方案二:手动启动后台线程的RunLoop(需谨慎处理)
如果必须在后台线程使用Timer,你需要手动启动该线程的RunLoop,同时要注意线程的生命周期,避免线程无限挂起:private func expireToken(afterTimeInterval timeInterval: TimeInterval) { print("[DEBUG] Schedule expiration, time: \(timeInterval), main thread \(Thread.isMainThread)") let timer = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false) { timer in print("[DEBUG] Expiring token") self.tokenContainer = .expired timer.invalidate() // Timer触发后退出RunLoop,避免线程一直挂着 RunLoop.current.stop() } // 启动RunLoop,这会阻塞当前线程直到RunLoop被停止 RunLoop.current.run() }注意:这种方式会阻塞当前后台线程,直到Timer触发或RunLoop被手动停止。如果是用临时后台线程执行这个方法,要确保线程不会被提前回收,同时处理好RunLoop的退出逻辑,防止资源浪费。
补充说明
你之前没遇到问题,大概率是之前的调用场景都是在主线程执行expireToken方法——主线程RunLoop一直处于运行状态,Timer能被正常调度。这次切换到后台线程后,RunLoop的运行机制差异就暴露出来了。
内容的提问来源于stack exchange,提问作者Michał Ziobro
相关产品推荐
相关产品推荐

