Swift 计时器

在 Swift 中,计时器通常有以下三种

  • Timer
  • DispatchSourceTimer
  • CADisplayLink

区别

原理不同

  • CADisplayLink指定的回调方法的调用频率和依赖于屏幕的刷新频率,每次刷新完调用一次回调方法。
  • Timer以指定的模式注册到 Runloop 后,每当设定的周期时间到达后,Runloop会向指定的target发送一次指定的selector消息。
  • Timer和CADisplayLink 的执行依赖 Runloop,DispatchSourceTimer 基于 GCD,不依赖 Runloop。

周期设置不同

  • CADisplayLink指定的回调方法在每次屏幕刷新完调用一次,也可以通过preferredFramesPerSecond属性设置每次调用回调方法的次数。
  • Timer 和 DispatchSourceTimer 可以简单直接设置单词执行和多次重复执行,CADisplayLink 不可设置单次执行。

精度不同

  • iOS设备的刷新频率很固定,因此CADisplayLink回调方法调用频率也很固定。
  • Timer 受 Runloop 影响,如果 Timer 到了触发时间,而这时候 Runloop 正在处理其他耗时的任务,那么本次 Timer 的调用会被漏掉。
  • DispatchSourceTimer 计时精度也比较高,同时也可以设置一个偏差值 leeway,系统可以使用 leeway 值来提前或延迟触发定时器。

使用场景不同

  • Timer 的使用范围比较广泛,各种需要单次或者循环定时处理的任务都可以使用,适用于对计时准确度要求不高的场景。
  • DispatchSourceTimer 相比 Timer 精度要高,更适合对计时准确度较高的场景。
  • CADisplayLink 使用场合相对专一, 适合做界面的不停重绘,比如视频播放的时候需要不停地获取下一帧用于界面渲染。
import Foundation let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in print("\(Thread.current) 主线程 Timer fired at \(Date())") } DispatchQueue(label: "2").async { let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in print("\(Thread.current) 其他线程 Timer fired at \(Date())") } RunLoop.current.add(timer, forMode: .common) RunLoop.current.run() // 其他线程默认不开启 Runloop,若不手动开启 Runloop,则该计时器无效。 }
import Foundation import Dispatch // 创建一个全局队列 let queue = DispatchQueue.global() // 创建一个 DispatchSourceTimer 对象 let timer = DispatchSource.makeTimerSource(queue: queue) // 设置定时器参数 let timeInterval: DispatchTimeInterval = .seconds(1) let leeway: DispatchTimeInterval = .milliseconds(100) timer.schedule(deadline: .now() + timeInterval, repeating: timeInterval, leeway: leeway) // 设置定时器触发事件 timer.setEventHandler { print("\(Thread.current) Timer fired at \(Date())") } // 启动定时器 timer.resume()
import UIKit class ViewController: UIViewController { var displayLink: CADisplayLink? var startTime: CFTimeInterval = 0.0 override func viewDidLoad() { super.viewDidLoad() // 创建 CADisplayLink 对象,关联到当前 run loop displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:))) displayLink?.preferredFramesPerSecond = 60 // 设置每秒钟执行的帧数,默认是 60 帧 // 将 CADisplayLink 添加到 run loop 中 displayLink?.add(to: .main, forMode: .default) // 记录开始时间 startTime = CACurrentMediaTime() } @objc func handleDisplayLink(_ displayLink: CADisplayLink) { // 计算当前时间 let currentTime = CACurrentMediaTime() // 计算经过的时间 let elapsedTime = currentTime - startTime // 在控制台打印当前时间 print("Current time: \(currentTime)") // 如果需要在某个条件下停止 CADisplayLink,可以调用 invalidate() 方法 if elapsedTime > 10.0 { displayLink.invalidate() } } }