场景1:子线程耗时计算 / 网络请求(最常用)
业务场景:图片解码、大数据计算、网络请求、文件读写、数据库查询,不能卡主线程。
原理:global 全局并发队列,系统线程池托管,不会爆线程。
// 后台异步执行耗时任务
DispatchQueue.global(qos: .userInitiated).async {
// 耗时操作:解析大图 / 遍历十万条数据 / 网络请求
sleep(1)
// 回到主线程刷新UI
DispatchQueue.main.async {
print("UI刷新完成")
}
}
QoS 优先级选择(记死这套)
.userInitiated:用户触发、需要快速展示(列表加载、下拉刷新).utility:持续任务(上传、下载、同步数据).background:极低优先级(日志、缓存清理)
绝对禁忌:UI 刷新不能在子线程!
场景2:延时执行(替代 Timer,简单精准)
业务场景:按钮防抖、3秒后弹窗、延迟加载动画、倒计时。
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
// 2秒后执行
print("延迟执行")
}
优势:不用销毁、不循环、不卡顿、比 Timer 轻量。
场景3:串行队列(保证任务有序、线程安全)
业务场景:
- 日志有序写入
- 数据库连续操作
- 瀑布流索引预计算(你十万条布局必备)
- 防止多线程读写冲突
特点:FIFO 先进先出,同一队列任务串行执行、不会并发
// 创建私有串行队列
let serialQueue = DispatchQueue(label: "com.waterfall.index")
serialQueue.async {
print("任务1:计算布局索引")
}
serialQueue.async {
print("任务2:保存索引缓存")
}
// 一定 1 先 2 后,顺序绝对不乱
场景4:sync 同步阻塞(慎用!死锁重点)
业务场景:读缓存、读取配置、必须拿到结果才能继续。
定义:sync = 卡住当前线程,等任务跑完再往下走。
let queue = DispatchQueue(label: "test")
print("开始")
queue.sync {
sleep(1)
print("同步任务完成")
}
print("结束") // 会卡1秒才执行
致命禁忌(官方重点)
❌ 主线程 sync 主队列 = 必死锁
// 绝对不要写!!死锁
DispatchQueue.main.sync { }
场景5:concurrentPerform 多核并行循环(超级提速)
业务场景:
- 批量图片压缩
- 批量解析数据
- 大量计算任务
作用:自动利用多核 CPU,比 for 循环快数倍
DispatchQueue.concurrentPerform(iterations: 100) { i in
// 100个任务并行跑
print("并行任务 (i)")
}
// 全部跑完才会往下走
场景6:DispatchGroup 等待一堆异步任务全部完成
业务场景:
- 同时发起多个网络请求,全部结束再刷新UI
- 多文件下载完成后统一回调
let group = DispatchGroup()
group.enter()
DispatchQueue.global().async {
sleep(1)
group.leave()
}
group.enter()
DispatchQueue.global().async {
sleep(2)
group.leave()
}
// 全部完成回调
group.notify(queue: .main) {
print("所有任务结束,刷新UI")
}
场景7:队列 target 优化(官方最佳实践)
官方文档重点:不要随便新建并发队列,会耗尽线程!
正确写法:自定义串行队列,target 挂靠全局队列
// 所有私有队列挂靠全局队列,防止线程爆炸
let safeQueue = DispatchQueue(label: "com.xxx.queue", target: .global())
作用:既保留串行顺序,又不新建多余线程资源。
场景8:你当前项目【十万条瀑布流】专属用法(核心)
你的需求:后台预计算10万条布局索引,不卡UI
最终标准写法(完全符合 Apple 规范)
/// 后台线程计算十万条瀑布流索引(不卡UI)
func buildWaterfallIndexAsync() {
// 后台低优先级、大量计算专用队列
DispatchQueue.global(qos: .utility).async {
// 十万次循环串行计算(瀑布流必须串行)
var columnHeights = [0.0, 0.0]
var indexTable: [WaterfallIndexItem] = []
for i in 0..<100000 {
// 计算最短列、记录Y坐标...
}
// 计算完毕切回主线程刷新CollectionView
DispatchQueue.main.async {
self.layout.indexTable = indexTable
self.collectionView.reloadData()
}
}
}
为什么必须这么写?
- 10万次循环必须子线程,否则主线程卡死
- 瀑布流布局强顺序依赖,不能并行
- 计算完必须主线程刷新UI
终极总结:日常开发 GCD 口诀
- 耗时任务 global.async
- UI 刷新 main.async
- 有序任务 自定义串行队列
- 多任务同时完成用 group
- 批量计算用 concurrentPerform
- 主线程绝对不能 sync
- 自定义队列务必挂 target 防止线程爆炸