1️⃣ 忘记 resume()
let source = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
source.schedule(deadline: .now(), repeating: 1.0)
source.setEventHandler { print("Timer fired") }
// source.resume() // ⚠️ 忘记 resume
- 坑:创建 DispatchSource 后必须调用
resume()才会启动事件源 - 原因:DispatchSource 默认是暂停状态
- 后果:事件永远不会触发
- 解决:调用
resume();如果需要暂停/恢复,结合suspend()使用
2️⃣ DispatchSource 被释放
DispatchSource.makeTimerSource(queue: .global()).schedule(deadline: .now(), repeating: 1.0).setEventHandler {
print("Fired")
}
- 坑:DispatchSource 没有被强引用 → 立即释放
- 原因:DispatchSource 是对象,如果没有外部引用,系统会释放它
- 后果:事件永远不会触发
- 解决:保留一个强引用,例如类属性
class MyClass {
var timer: DispatchSourceTimer?
func start() {
timer = DispatchSource.makeTimerSource(queue: .global())
timer?.schedule(deadline: .now(), repeating: 1.0)
timer?.setEventHandler { print("Fired") }
timer?.resume()
}
}
3️⃣ 线程安全误区
-
坑:认为事件处理 block 内访问的资源线程安全
-
原因:DispatchSource 的回调在指定队列上执行,并发队列上可能同时执行多个事件 block
-
后果:共享资源竞争、数据不一致
-
解决:
- 串行队列保证顺序和互斥
- 并发队列访问共享资源时加锁或 barrier
4️⃣ 重复 resume 导致 crash
source.resume()
source.resume() // ⚠️ 再次 resume 会 crash
-
坑:DispatchSource 只能 resume 一次
-
原因:resume 用来启动事件源,重复调用会触发异常
-
解决:
- 使用标志位判断是否已 resume
- 或通过类属性封装管理状态
5️⃣ 使用 sync 触发死锁
let queue = DispatchQueue(label: "serial")
let source = DispatchSource.makeTimerSource(queue: queue)
source.setEventHandler {
queue.sync { print("Deadlock") } // ⚠️ 死锁
}
source.resume()
- 坑:事件 block 内调用 sync 队列同队列 → 死锁
- 原因:串行队列同步调用自己会等待 → 永远无法完成
- 解决:使用 async 调度,或者将事件 block 放到不同队列
6️⃣ 忘记 cancel
- 坑:DispatchSource 用完不取消
- 原因:DispatchSource 会保持队列引用,未 cancel 可能导致内存泄漏
- 解决:
source.cancel()
source.setEventHandler(nil) // 释放闭包引用
7️⃣ 对定时器误用 resume/suspend
-
坑:误认为 suspend/resume 可以无限暂停定时器
-
注意:
- DispatchSourceTimer 初始为暂停状态 → 必须 resume 启动
- suspend/resume 必须成对使用,否则下一次 resume 会 crash
8️⃣ 队列选择不当
-
坑:将 DispatchSource 绑定到主队列或串行队列,但事件量大
-
后果:
- 阻塞主线程 → UI 卡顿
- 串行队列上高频事件 → 队列积压
-
解决:
- 高频事件使用后台并发队列
- 根据场景合理选择队列类型
✅ 总结常见坑及解决策略
| 坑 | 解决方案 |
|---|---|
| 忘记 resume | 必须调用 resume() 启动事件源 |
| DispatchSource 被释放 | 保持强引用(类属性) |
| 线程安全误区 | 并发队列访问共享资源加锁,或使用串行队列 |
| 重复 resume 导致 crash | 只 resume 一次,使用标志位 |
| sync 导致死锁 | 避免在事件 block 内对同队列 sync,使用 async |
| 忘记 cancel | 使用完毕后 cancel,并释放事件处理器闭包 |
| suspend/resume 不成对 | 严格成对调用,确保队列状态正确 |
| 队列选择不当 | 高频事件用后台并发队列,UI 事件用主队列 |
💡 核心经验:
- DispatchSource 本质是 事件源 + GCD block → 避免直接对线程或队列做 unsafe 操作
- 所有事件处理 block 都遵循绑定队列调度 → 理解串行/并发对共享资源的影响
- 强引用 + resume + cancel 是生命周期管理三部曲