由浅入深,从基本概念到源码解析,带你全面理解 iOS 并发编程
一、为什么需要多线程? 1.1 单线程的局限 在移动应用中,主线程(Main Thread/UI Thread) 负责:
处理用户交互(点击、滑动等)
更新 UI
处理 RunLoop 事件
如果耗时操作(网络请求、大文件读写、复杂计算)在主线程执行,会导致:
界面卡顿 :主线程被阻塞,无法及时响应触摸
ANR(Application Not Responding) :系统可能强制终止「无响应」的 App
糟糕的用户体验
1 2 3 4 5 6 func loadData () { let url = URL (string: "https://api.example.com/data" )! let data = try? Data (contentsOf: url) self .tableView.reloadData() }
1.2 多线程的核心思想 将耗时任务放到子线程 执行,完成后回到主线程 更新 UI:
1 2 3 主线程:响应用户 → 派发任务到子线程 → 继续处理 UI 子线程:执行耗时任务 → 完成 → 通知主线程 主线程:收到结果 → 更新 UI
二、iOS 多线程技术栈 2.1 技术对比
技术
抽象层次
使用场景
学习曲线
Thread
底层,直接操作线程
需要精细控制线程生命周期
高
GCD
任务队列,无需管理线程
绝大多数异步任务
中
Operation
面向对象,可取消/依赖/优先级
复杂任务编排
中低
2.2 选择建议
首选 GCD :简单异步、串行/并发队列、延迟执行、一次性执行
Operation :需要取消、依赖关系、暂停恢复、进度回调
Thread :极少需要,仅当必须直接操作线程时使用
三、GCD(Grand Central Dispatch)详解 3.1 核心概念 GCD 是苹果提供的并发编程框架 ,基于 C 库 libdispatch 。它采用「任务 + 队列」模型:
任务(Block/Closure) :要执行的代码块
队列(Queue) :存放任务,按规则调度到线程执行
1 2 3 4 5 6 7 8 9 DispatchQueue .global().async { let result = doHeavyWork() DispatchQueue .main.async { self .updateUI(result) } }
3.2 队列类型
队列
类型
说明
主队列
串行
DispatchQueue.main,主线程执行,用于 UI 更新
全局队列
并发
DispatchQueue.global(qos:),系统管理线程池
自定义串行队列
串行
同一时刻只执行一个任务
自定义并发队列
并发
可同时执行多个任务
1 2 3 4 5 6 7 8 9 let serialQueue = DispatchQueue (label: "com.app.serial" )let concurrentQueue = DispatchQueue (label: "com.app.concurrent" , attributes: .concurrent)DispatchQueue .global(qos: .userInitiated).async { } DispatchQueue .global(qos: .background).async { }
3.3 QoS 优先级
QoS
说明
典型场景
.userInteractive
用户交互,最高优先级
动画、即时反馈
.userInitiated
用户发起,高优先级
加载数据、点击后处理
.default
默认
无特别需求
.utility
实用型
下载、导入
.background
后台
同步、预加载
3.4 常用 API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 queue.async { } queue.sync { } DispatchQueue .main.asyncAfter(deadline: .now() + 2.0 ) { }var token: Int = 0 DispatchQueue .once(& token) { } let workItem = DispatchWorkItem { print ("working" ) }queue.async(execute: workItem) workItem.cancel()
3.5 dispatch_barrier:读写锁场景 在自定义并发队列 中,barrier 可保证「屏障前的任务完成后,再执行屏障任务,屏障完成后再执行屏障后的任务」:
1 2 3 4 5 6 7 8 9 let concurrentQueue = DispatchQueue (label: "com.app.db" , attributes: .concurrent)concurrentQueue.async { self .readFromCache() } concurrentQueue.async(flags: .barrier) { self .writeToCache(newValue) }
四、NSOperation 与 NSOperationQueue 4.1 为什么需要 Operation? GCD 虽然强大,但在以下场景不够灵活:
需要取消 尚未执行的任务
需要任务依赖 (A 完成后再执行 B)
需要暂停/恢复 队列
需要进度 、完成回调 等
Operation 提供了面向对象的方式解决这些问题。
4.2 基本用法 1 2 3 4 5 6 7 8 9 10 11 let op = BlockOperation { print ("执行任务" ) } op.completionBlock = { print ("任务完成" ) } let queue = OperationQueue ()queue.addOperation(op) queue.maxConcurrentOperationCount = 3
4.3 任务依赖 1 2 3 4 5 6 7 8 let opA = BlockOperation { downloadImage() }let opB = BlockOperation { resizeImage() }let opC = BlockOperation { uploadImage() }opB.addDependency(opA) opC.addDependency(opB) queue.addOperations([opA, opB, opC], waitUntilFinished: false )
4.4 自定义 Operation 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class ImageLoadOperation : Operation { let url: URL var image: UIImage ? init (url : URL ) { self .url = url super .init () } override var isAsynchronous: Bool { true } override func main () { guard ! isCancelled else { return } if let data = try? Data (contentsOf: url) { image = UIImage (data: data) } } }
4.5 取消与暂停 1 2 3 queue.cancelAllOperations() queue.isSuspended = true queue.isSuspended = false
五、线程同步与线程安全 5.1 为什么需要同步? 多个线程同时访问共享资源 (变量、文件、网络连接)时,若未做同步,会出现:
数据竞争 :读写交错,结果不可预期
脏读 :读到未写入完成的数据
崩溃 :如数组在遍历时被另一线程修改
1 2 3 4 5 6 var counter = 0 DispatchQueue .concurrentPerform(iterations: 1000 ) { _ in counter += 1 } print (counter)
5.2 锁机制 5.2.1 NSLock 1 2 3 4 5 6 7 8 9 let lock = NSLock ()var counter = 0 DispatchQueue .concurrentPerform(iterations: 1000 ) { _ in lock.lock() defer { lock.unlock() } counter += 1 } print (counter)
5.2.2 os_unfair_lock(高性能,iOS 10+) 1 2 3 4 var unfairLock = os_unfair_lock()os_unfair_lock_lock(& unfairLock) os_unfair_lock_unlock(& unfairLock)
5.2.3 NSRecursiveLock(可重入锁) 同一线程可多次加锁,用于递归或嵌套调用:
1 2 3 4 5 6 7 8 let recursiveLock = NSRecursiveLock ()func recursiveMethod (_ n : Int ) { recursiveLock.lock() defer { recursiveLock.unlock() } if n > 0 { recursiveMethod(n - 1 ) } }
5.2.4 @synchronized(Objective-C) 1 2 3 @synchronized (self ) { }
底层使用 objc_sync_enter / objc_sync_exit,基于对象做锁。
5.3 信号量(Semaphore) 控制并发数量 或实现生产者-消费者 :
1 2 3 4 5 6 7 8 9 let semaphore = DispatchSemaphore (value: 3 ) for i in 0 ..< 10 { DispatchQueue .global().async { semaphore.wait() defer { semaphore.signal() } doWork(i) } }
5.4 原子操作(Atomic) 对于简单类型,可使用原子属性。Swift 中常用 NSLock + 属性封装,或使用 objc_setAssociatedObject 的原子选项。atomic 属性只保证 getter/setter 原子,不保证复合操作(如 count++)的原子性。
5.5 避免死锁 死锁 :两个或多个线程互相等待对方释放资源。
1 2 3 4 DispatchQueue .main.sync { print ("永远不会执行" ) }
1 2 3 4 5 6 7 let queue = DispatchQueue (label: "serial" )queue.async { queue.sync { print ("死锁" ) } }
原则 :避免在同一串行队列 中嵌套 sync 调用。
六、Swift 并发(async/await) 6.1 从回调到 async/await 传统异步代码容易产生「回调地狱」:
1 2 3 4 5 6 7 8 loadUser(id: 1 ) { user in loadPosts(userId: user.id) { posts in loadComments(postId: posts[0 ].id) { comments in } } }
Swift 5.5 引入 async/await ,写法更清晰:
1 2 3 4 5 6 func loadUserData () async throws { let user = try await loadUser(id: 1 ) let posts = try await loadPosts(userId: user.id) let comments = try await loadComments(postId: posts[0 ].id) await MainActor .run { self .updateUI(comments) } }
6.2 Task 与 MainActor 1 2 3 4 5 6 7 8 9 10 11 12 13 Task { let result = await fetchData() await MainActor .run { self .label.text = result } } @MainActor class ViewController : UIViewController { func updateUI () { } }
6.3 与 GCD 的桥接 1 2 3 4 5 6 7 8 9 func withCheckedContinuation () async { await withCheckedContinuation { continuation in DispatchQueue .global().async { let result = doWork() continuation.resume(returning: result) } } }
七、RunLoop 与线程 每个线程都有唯一的 RunLoop。子线程默认不启动 RunLoop,若使用 performSelector:onThread: 或 NSTimer,需要手动 run:
1 2 3 4 5 6 class WorkerThread : Thread { override func main () { RunLoop .current.add(Port (), forMode: .default) RunLoop .current.run() } }
主线程的 RunLoop 由系统自动运行,通常无需关心。
八、GCD 底层原理(libdispatch 简析) 8.1 队列与线程池
GCD 维护全局线程池 ,根据队列类型和系统负载动态创建/复用线程
串行队列 :通常绑定一个线程,任务在该线程顺序执行
并发队列 :任务可分发到线程池中的多个线程
8.2 任务提交流程(简化) 1 2 3 4 5 6 7 dispatch_async(queue, block) │ ├─ 将 block 封装为 dispatch_continuation_t │ ├─ 将任务加入队列的 FIFO 链表 │ └─ 若有空闲 worker 线程,则唤醒执行;否则根据需要创建新线程
8.3 主队列特殊性 主队列任务一定在主线程执行,通过 RunLoop 的 Source1(Mach Port)唤醒主线程处理。
8.4 源码参考 libdispatch 开源:https://github.com/apple/swift-corelibs-libdispatch
核心结构体(简化):
1 2 3 4 5 6 7 8 9 struct dispatch_queue_s { }; struct dispatch_continuation_s { void (*dc_func)(void *); void *dc_ctxt; };
九、实战示例 9.1 图片异步加载与缓存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class ImageLoader { private let cache = NSCache <NSString , UIImage >() private let queue = DispatchQueue (label: "com.app.imageloader" , attributes: .concurrent) func loadImage (url : URL , completion : @escaping (UIImage ?) -> Void ) { let key = url.absoluteString as NSString if let cached = cache.object(forKey: key) { DispatchQueue .main.async { completion(cached) } return } queue.async { guard let data = try? Data (contentsOf: url), let image = UIImage (data: data) else { DispatchQueue .main.async { completion(nil ) } return } self .cache.setObject(image, forKey: key) DispatchQueue .main.async { completion(image) } } } }
9.2 多接口并发请求,统一回调 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 func loadDashboardData (completion : @escaping (DashboardData ?) -> Void ) { let group = DispatchGroup () var user: User ? var orders: [Order ]? var error: Error ? group.enter() fetchUser { result in user = try? result.get() group.leave() } group.enter() fetchOrders { result in orders = (try? result.get()) ?? [] group.leave() } group.notify(queue: .main) { if let u = user, let o = orders { completion(DashboardData (user: u, orders: o)) } else { completion(nil ) } } }
9.3 线程安全的单例 1 2 3 4 5 6 7 8 final class DataManager { static let shared: DataManager = { let instance = DataManager () return instance }() private init () { } }
若需「懒加载 + 线程安全」,可使用 dispatch_once 的 Swift 封装,或在 Swift 中依赖 static let 的天然懒加载与线程安全。
十、实际项目中的应用案例 10.1 列表图片预加载 在 UITableView / UICollectionView 滚动时,预加载即将出现的 cell 所需图片,放到后台队列解码,再回主线程赋值,避免主线程卡顿。
1 2 3 4 5 6 7 8 9 10 11 func prefetchImage (at indexPath : IndexPath ) { let url = urls[indexPath.row] DispatchQueue .global(qos: .utility).async { guard let data = try? Data (contentsOf: url), let image = UIImage (data: data) else { return } DispatchQueue .main.async { self .imageCache.setObject(image, forKey: url.absoluteString as NSString ) self .collectionView.reloadItems(at: [indexPath]) } } }
10.2 大文件分片上传 将大文件切分为多个 chunk,通过 OperationQueue 控制并发数,每个 Operation 上传一个 chunk,全部完成后组装结果。
1 2 3 4 5 6 7 8 let uploadQueue = OperationQueue ()uploadQueue.maxConcurrentOperationCount = 3 for chunk in chunks { let op = BlockOperation { uploadChunk(chunk) } uploadQueue.addOperation(op) } uploadQueue.waitUntilAllOperationsAreFinished() mergeChunks()
10.3 搜索防抖 用户输入时,取消上一次未完成的搜索任务,延迟 300ms 再发起新请求:
1 2 3 4 5 6 7 8 9 var searchWorkItem: DispatchWorkItem ?func searchTextDidChange (_ text : String ) { searchWorkItem? .cancel() let workItem = DispatchWorkItem { [weak self ] in self ? .performSearch(text) } searchWorkItem = workItem DispatchQueue .main.asyncAfter(deadline: .now() + 0.3 , execute: workItem) }
10.4 后台数据同步 App 进入后台时,使用 beginBackgroundTask 申请有限时间,在后台队列执行同步逻辑,完成后结束 task:
1 2 3 4 5 6 7 8 9 10 11 12 func syncInBackground () { var taskID: UIBackgroundTaskIdentifier = .invalid taskID = UIApplication .shared.beginBackgroundTask { UIApplication .shared.endBackgroundTask(taskID) } DispatchQueue .global(qos: .utility).async { performSync() DispatchQueue .main.async { UIApplication .shared.endBackgroundTask(taskID) } } }
十一、常见问题与最佳实践 11.1 主线程检查 开发阶段可用断言检查 UI 是否在主线程更新:
1 assert (Thread .isMainThread, "UI must be updated on main thread" )
11.2 避免循环引用 在闭包中使用 self 时注意 [weak self]:
1 2 3 4 5 6 7 DispatchQueue .global().async { [weak self ] in guard let self = self else { return } let result = self .doWork() DispatchQueue .main.async { [weak self ] in self ? .updateUI(result) } }
11.3 合理选择 QoS 不要滥用 .userInteractive,避免后台任务抢占用户交互资源。
11.4 优先使用 Swift 并发 新项目优先考虑 async/await + Task,结构更清晰,可组合性更好。
十二、总结
场景
推荐方案
简单异步、延迟执行
GCD
复杂任务依赖、取消、暂停
NSOperation
新项目、网络/IO 密集型
async/await
控制并发数
信号量 或 OperationQueue.maxConcurrentOperationCount
读写分离、缓存
dispatch_barrier
线程安全访问共享资源
NSLock / os_unfair_lock
多线程能提升体验,但也会带来复杂度和潜在问题。理解原理、选对工具、注意同步与线程安全,是写出高质量并发代码的关键。