在 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()
}
}
}