1-19.【GCD】DispatchSourceTimer 和 Timer 的核心区别?

2 阅读3分钟

DispatchSourceTimer(GCD Timer)和 Timer(NSTimer)都是定时器,但它们在底层实现、线程模型、精度和性能上有本质区别。下面系统分析。


1️⃣ 核心区别一句话

DispatchSourceTimer 基于 GCD 事件源,异步、线程安全、高精度;NSTimer 基于 RunLoop,需要 RunLoop 驱动,精度低且受线程阻塞影响。


2️⃣ 线程和执行模型

特性DispatchSourceTimerTimer / NSTimer
执行队列GCD 队列(串行或并发)RunLoop 所在线程
是否线程安全❌(必须在 RunLoop 所在线程访问)
是否阻塞线程❌(事件派发)❌,但 RunLoop 阻塞会影响触发
执行线程可控✅(可指定 DispatchQueue)✖(必须在 RunLoop 所在线程)
  • DispatchSourceTimer 可以在任何队列异步执行,适合后台并发或 barrier 配合使用
  • NSTimer 必须添加到 RunLoop 才能触发,阻塞或 RunLoop 休眠会延迟触发

3️⃣ 精度和性能

  • DispatchSourceTimer

    • 基于内核底层 timer(kqueue / mach port)
    • 高精度,系统调度开销小
    • 可重复触发,误差小
  • NSTimer

    • 依赖 RunLoop 的循环
    • 受 RunLoop 模式、阻塞和 UI 线程调度影响
    • 精度较低,长时间累积可能漂移

4️⃣ 控制能力

功能DispatchSourceTimerNSTimer
延迟启动✅(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️⃣ 总结对比

特性DispatchSourceTimerNSTimer
底层GCD / 内核事件源RunLoop
精度中等 / 低
线程依赖GCD 队列可控RunLoop 所在线程
暂停 / 恢复
使用场景高精度后台定时、并发任务UI 定时、简单重复事件

💡 一句话概括
DispatchSourceTimer = 高性能、可控、线程安全的 GCD 定时器;NSTimer = RunLoop 驱动、UI 友好的定时器,但精度和线程控制受限。


👉 追问:为什么 DispatchSourceTimer 更稳定?

DispatchSourceTimerNSTimer 更稳定的根本原因在于它 底层依赖内核事件源 + GCD 队列调度,而不是依赖 RunLoop 循环。下面系统分析。


1️⃣ 执行机制差异

特性DispatchSourceTimerNSTimer
触发依赖内核定时器 + GCD 队列RunLoop 循环
阻塞影响❌ 阻塞当前线程不会影响其他队列✅ RunLoop 阻塞会延迟触发
执行线程可绑定任意 GCD 队列必须在 RunLoop 所在线程
调度精度高(内核级调度)中等 / 低(RunLoop 轮询)
  • DispatchSourceTimer 是事件驱动的:

    • 底层内核生成事件
    • GCD 队列派发事件异步执行
    • 不依赖当前线程空闲与否
  • NSTimer 是轮询驱动的:

    • RunLoop 每一轮检查 timer
    • 如果 RunLoop 阻塞或切换模式 → 触发延迟

2️⃣ 精度和漂移控制

  • DispatchSourceTimer

    • 可以设置 deadlinerepeating
    • 系统内核管理触发时间
    • 高精度、累积误差小
  • 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️⃣ 总结稳定性原因

  1. 内核事件源:定时触发由操作系统管理,不依赖轮询
  2. GCD 队列调度:任务异步派发,阻塞调用线程不影响定时器触发
  3. 可控线程:绑定任意队列,不依赖主线程
  4. 累积误差小:系统维护精确触发时间,重复周期稳定

一句话总结
DispatchSourceTimer 更稳定,因为它是 内核驱动 + GCD 异步调度,不受 RunLoop 阻塞或线程负载影响,而 NSTimer 受限于 RunLoop 执行周期和线程状态。