DispatchQueue 最全实战场景(可直接上线使用)

2 阅读3分钟

场景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 防止线程爆炸