Swift 异步编程简介
现代基于 Swift (iOS/macOS) 的移动和桌面应用程序越来越需要执行长时间运行的操作——从网络加载数据、处理文件、图像处理——而不阻塞用户界面。直到最近,开发者还使用闭包 (completion handlers) 和 Grand Central Dispatch (GCD),这导致了所谓的"回调地狱"并使代码难以阅读。随着 Swift 5.5 的发布,Apple 推出了一种全新的异步编程模型,基于关键字 async 和 await,以及 actors 和 Task 结构。在本指南中,我们将通过实际示例讲解所有关键概念。
1. Swift 中的 async/await 基础
关键字 async 表示函数或方法可以暂停其执行以等待异步操作的结果。调用此类函数时使用 await 标记。这使得编写异步代码可以像同步代码一样线性。
// 声明一个异步函数func fetchUserData(from url: URL) async throws -> Data { let (data, _) = try await URLSession.shared.data(from: url) return data}
// 在任务上下文中使用它Task { do { let url = URL(string: "https://api.example.com/user")! let userData = try await fetchUserData(from: url) print("数据已获取: \\(userData.count) 字节") } catch { print("加载错误: \\(error.localizedDescription)") }}请注意:await 调用会暂停当前任务,但不会阻塞线程。当结果准备好时,系统会自动切换上下文,这使得代码高效且安全。
2. Actors — 安全的状态管理
异步性带来了数据竞争 (data race) 问题。为了解决这个问题,Swift 引入了 actors。Actor 是一种引用类型,它隔离自己的状态,并保证在任何时刻只有一个任务可以访问其可变属性。
actor Counter { private var value = 0 func increment() { value += 1 } func getValue() -> Int { return value }}
// 使用 actorlet counter = Counter()
Task { await counter.increment() let currentValue = await counter.getValue() print("当前值: \\(currentValue)")}关键字 actor 会自动使所有方法和属性(除了不可变的)对外部访问变为异步。这避免了手动管理锁 (NSLock, DispatchSemaphore) 时常见的死锁 (deadlocks) 问题。
3. TaskGroup 和并行任务执行
当需要并行执行多个独立的异步操作时,使用 TaskGroup。这是一种结构,允许启动子任务并收集其结果。
func fetchMultipleUsers(ids: [Int]) async throws -> [User] { try await withThrowingTaskGroup(of: User.self) { group in var users: [User] = [] for id in ids { group.addTask { let url = URL(string: "https://api.example.com/users/\\(id)")! let data = try await URLSession.shared.data(from: url) return try JSONDecoder().decode(User.self, from: data.0) } } for try await user in group { users.append(user) } return users }}
// 使用Task { let users = try? await fetchMultipleUsers(ids: [1, 2, 3, 4, 5]) print("已加载用户数: \\(users?.count ?? 0)")}方法 withThrowi