DispatchSourceTimer(GCD Timer)和 Timer(NSTimer)都是定时器,但它们在底层实现、线程模型、精度和性能上有本质区别。下面系统分析。
1️⃣ 核心区别一句话
DispatchSourceTimer 基于 GCD 事件源,异步、线程安全、高精度;NSTimer 基于 RunLoop,需要 RunLoop 驱动,精度低且受线程阻塞影响。
2️⃣ 线程和执行模型
| 特性 | DispatchSourceTimer | Timer / NSTimer |
|---|---|---|
| 执行队列 | GCD 队列(串行或并发) | RunLoop 所在线程 |
| 是否线程安全 | ✅ | ❌(必须在 RunLoop 所在线程访问) |
| 是否阻塞线程 | ❌(事件派发) | ❌,但 RunLoop 阻塞会影响触发 |
| 执行线程可控 | ✅(可指定 DispatchQueue) | ✖(必须在 RunLoop 所在线程) |
- DispatchSourceTimer 可以在任何队列异步执行,适合后台并发或 barrier 配合使用
- NSTimer 必须添加到 RunLoop 才能触发,阻塞或 RunLoop 休眠会延迟触发
3️⃣ 精度和性能
-
DispatchSourceTimer
- 基于内核底层 timer(kqueue / mach port)
- 高精度,系统调度开销小
- 可重复触发,误差小
-
NSTimer
- 依赖 RunLoop 的循环
- 受 RunLoop 模式、阻塞和 UI 线程调度影响
- 精度较低,长时间累积可能漂移
4️⃣ 控制能力
| 功能 | DispatchSourceTimer | NSTimer |
|---|---|---|
| 延迟启动 | ✅(schedule(deadline:)) | ✅(fireDate) |
| 重复 | ✅(repeating:) | ✅(repeats) |
| 暂停 / 恢复 | ✅(suspend / resume) | ❌(只能 invalidate) |
| 取消 | ✅(cancel) | ✅(invalidate) |
| 队列绑定 | ✅(DispatchQueue) | ✖(只能绑定 RunLoop) |
5️⃣ 使用示例
DispatchSourceTimer
let queue = DispatchQueue.global()
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline: .now(), repeating: 1.0)
timer.setEventHandler { print("GCD Timer fired") }
timer.resume()
- 异步执行在
queue - 高精度,可后台运行
- 可暂停 / 恢复
NSTimer
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
print("NSTimer fired")
}
RunLoop.current.add(timer, forMode: .default)
- 依赖当前线程 RunLoop
- UI 阻塞或切换 RunLoop 模式可能延迟触发
6️⃣ 总结对比
| 特性 | DispatchSourceTimer | NSTimer |
|---|---|---|
| 底层 | GCD / 内核事件源 | RunLoop |
| 精度 | 高 | 中等 / 低 |
| 线程依赖 | GCD 队列可控 | RunLoop 所在线程 |
| 暂停 / 恢复 | ✅ | ❌ |
| 使用场景 | 高精度后台定时、并发任务 | UI 定时、简单重复事件 |
💡 一句话概括:
DispatchSourceTimer = 高性能、可控、线程安全的 GCD 定时器;NSTimer = RunLoop 驱动、UI 友好的定时器,但精度和线程控制受限。
👉 追问:为什么 DispatchSourceTimer 更稳定?
DispatchSourceTimer 比 NSTimer 更稳定的根本原因在于它 底层依赖内核事件源 + GCD 队列调度,而不是依赖 RunLoop 循环。下面系统分析。
1️⃣ 执行机制差异
| 特性 | DispatchSourceTimer | NSTimer |
|---|---|---|
| 触发依赖 | 内核定时器 + GCD 队列 | RunLoop 循环 |
| 阻塞影响 | ❌ 阻塞当前线程不会影响其他队列 | ✅ RunLoop 阻塞会延迟触发 |
| 执行线程 | 可绑定任意 GCD 队列 | 必须在 RunLoop 所在线程 |
| 调度精度 | 高(内核级调度) | 中等 / 低(RunLoop 轮询) |
-
DispatchSourceTimer 是事件驱动的:
- 底层内核生成事件
- GCD 队列派发事件异步执行
- 不依赖当前线程空闲与否
-
NSTimer 是轮询驱动的:
- RunLoop 每一轮检查 timer
- 如果 RunLoop 阻塞或切换模式 → 触发延迟
2️⃣ 精度和漂移控制
-
DispatchSourceTimer:
- 可以设置
deadline和repeating - 系统内核管理触发时间
- 高精度、累积误差小
- 可以设置
-
NSTimer:
- 每轮触发依赖 RunLoop
- 累积延迟容易发生(尤其在 UI 阻塞时)
示例:
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
timer.schedule(deadline: .now(), repeating: 1.0)
timer.setEventHandler { print(Date()) }
timer.resume()
- 每秒触发非常稳定
- 即使主线程被阻塞,GCD 队列仍然可以触发
3️⃣ 线程安全和队列隔离
- DispatchSourceTimer 可以绑定 任何队列,和其他任务并发执行
- NSTimer 必须绑定 RunLoop,如果 RunLoop 阻塞或模式切换 → 触发不稳定
4️⃣ 总结稳定性原因
- 内核事件源:定时触发由操作系统管理,不依赖轮询
- GCD 队列调度:任务异步派发,阻塞调用线程不影响定时器触发
- 可控线程:绑定任意队列,不依赖主线程
- 累积误差小:系统维护精确触发时间,重复周期稳定
一句话总结:
DispatchSourceTimer 更稳定,因为它是 内核驱动 + GCD 异步调度,不受 RunLoop 阻塞或线程负载影响,而 NSTimer 受限于 RunLoop 执行周期和线程状态。