并发与多线程在Swift中的高级应用
并发(concurrency)是iOS/macOS开发中最复杂和最重要的主题之一。在高级层面,Swift提供了强大的工具:从经典的Grand Central Dispatch(GCD)到现代的async/await模型和严格的Actor系统。本文将深入探讨如何高效管理线程、避免数据竞争以及编写线程安全代码。
1. Grand Central Dispatch (GCD):队列与组操作
GCD仍然是Swift多线程编程的基础。但在高级层面,不仅要理解main和global()的区别,更要掌握任务依赖管理。
创建自定义队列
let serialQueue = DispatchQueue(label: "com.pythonlib.serial")let concurrentQueue = DispatchQueue(label: "com.pythonlib.concurrent", attributes: .concurrent)
serialQueue.async { print("Task 1 in serial queue")}concurrentQueue.async { print("Task 2 in concurrent queue")}DispatchGroup:多任务同步
let group = DispatchGroup()let queue = DispatchQueue.global()
queue.async(group: group) { sleep(1) print("Loading data 1")}queue.async(group: group) { sleep(2) print("Loading data 2")}
group.notify(queue: DispatchQueue.main) { print("All loads complete, updating UI")}当需要等待多个并行操作完成时(例如图片加载或JSON解析),使用DispatchGroup。
2. 现代async/await模型与Task
从Swift 5.5开始,语言通过async和await关键字原生支持异步编程。这简化了代码可读性,避免了"闭包金字塔"。
声明与调用异步函数
func fetchUserData() async -> String { // Simulating network request try? await Task.sleep(nanoseconds: 2_000_000_000) return "User data loaded"}
Task { let result = await fetchUserData() print(result) // Prints "User data loaded"}使用async let并行调用
func loadImage() async -> Data { return Data() }func loadMetadata() async -> String { return "meta" }
Task { async let image = loadImage() async let metadata = loadMetadata() let result = try await (image, metadata) print("Image: \\(result.0), metadata: \\(result.1)")}async let允许并行启动多个异步任务并等待所有任务完成。这比顺序调用更高效。
3. Actors:语言级别的线程安全
Actors是一种防止数据竞争的新机制。它们自动保证对其状态的访问是同步的。
定义与使用Actor
actor BankAccount { private var balance: Double = 0.0 func deposit(amount: Double) { balance += amount print("Balance after deposit: \\(balance)") } func withdraw(amount: Double) -> Bool { guard balance >= amount else { return false } balance -= amount return true } func getBalance() -> Double { balance }}
Task { let account = BankAccount() await account.deposit(amount: 100) let success = await account.withdraw(amount: 50) print("Withdrawal successful: \\(success)")}所有Actor方法默认都是隔离的——只能通过await调用。这避免了多线程意外修改数据。
非隔离方法(nonisolated)
actor Logger { nonisolated func log(message: String) { print("Log: \\(message)")