Swift 新并发框架之 async/await 详解
目录
概述
Swift 5.5 引入了全新的并发模型,其中 async/await 是核心特性。这个新框架提供了一种更简洁、更安全的方式来处理异步代码,解决了传统回调地狱(Callback Hell)和复杂的 GCD 代码问题。
关键特性
- async/await: 异步函数声明和等待
- Task: 结构化并发任务
- Actor: 数据隔离和线程安全
- AsyncSequence: 异步序列
- TaskGroup: 并发任务组
为什么需要新的并发框架
传统异步编程的痛点
1. 回调地狱(Callback Hell)
// 传统回调方式 - 嵌套层级深,难以维护
func fetchUserData(completion: @escaping (Result<User, Error>) -> Void) {
fetchUserID { result in
switch result {
case .success(let userID):
fetchUserProfile(userID: userID) { result in
switch result {
case .success(let profile):
fetchUserPosts(userID: userID) { result in
switch result {
case .success(let posts):
let user = User(profile: profile, posts: posts)
completion(.success(user))
case .failure(let error):
completion(.failure(error))
}
}
case .failure(let error):
completion(.failure(error))
}
}
case .failure(let error):
completion(.failure(error))
}
}
}
2. 错误处理复杂
- 每层回调都需要处理错误
- 错误传递路径不清晰
- 容易遗漏错误处理
3. 线程管理困难
- GCD 的队列管理复杂
- 容易出现数据竞争
- 难以追踪执行上下文
4. 代码可读性差
- 执行顺序不直观
- 难以理解程序流程
- 维护成本高
async/await 基础
声明异步函数
// 使用 async 关键字声明异步函数
func fetchData() async -> Data {
// 异步操作
return data
}
// 可以抛出错误的异步函数
func fetchData() async throws -> Data {
// 可能失败的异步操作
return data
}
调用异步函数
// 使用 await 关键字等待异步函数完成
func loadUserData() async {
let data = await fetchData()
print("数据加载完成: \(data)")
}
// 处理可能抛出错误的异步函数
func loadUserData() async throws {
do {
let data = try await fetchData()
print("数据加载完成: \(data)")
} catch {
print("加载失败: \(error)")
}
}
核心概念
1. Task - 异步任务
Task 是 Swift 并发的基本执行单元。
// 创建独立任务
Task {
let result = await fetchData()
print(result)
}
// 带优先级的任务
Task(priority: .high) {
await performCriticalOperation()
}
// 取消任务
let task = Task {
await longRunningOperation()
}
task.cancel()
// 检查任务是否被取消
func processItems() async {
for item in items {
if Task.isCancelled {
print("任务已取消")
return
}
await process(item)
}
}
2. Actor - 数据隔离
Actor 提供了线程安全的数据访问机制。
actor BankAccount {
private var balance: Double = 0
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) -> Bool {
guard balance >= amount else { return false }
balance -= amount
return true
}
func getBalance() -> Double {
return balance
}
}
// 使用 Actor
let account = BankAccount()
Task {
await account.deposit(amount: 100)
let balance = await account.getBalance()
print("余额: \(balance)")
}
3. AsyncSequence - 异步序列
处理异步数据流。
// 自定义异步序列
struct AsyncCountdown: AsyncSequence {
typealias Element = Int
let start: Int
struct AsyncIterator: AsyncIteratorProtocol {
var current: Int
mutating func next() async -> Int? {
guard current >= 0 else { return nil }
let value = current
current -= 1
try? await Task.sleep(nanoseconds: 1_000_000_000) // 1秒
return value
}
}
func makeAsyncIterator() -> AsyncIterator {
return AsyncIterator(current: start)
}
}
// 使用异步序列
for await number in AsyncCountdown(start: 5) {
print(number)
}
4. TaskGroup - 并发任务组
同时执行多个任务并收集结果。
func fetchMultipleUsers(ids: [Int]) async throws -> [User] {
try await withThrowingTaskGroup(of: User.self) { group in
for id in ids {
group.addTask {
try await fetchUser(id: id)
}
}
var users: [User] = []
for try await user in group {
users.append(user)
}
return users
}
}
实战示例
示例 1: 网络请求
传统方式(URLSession + Completion Handler)
func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
let url = URL(string: "https://api.example.com/users/\(id)")!
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
completion(.failure(NetworkError.noData))
return
}
do {
let user = try JSONDecoder().decode(User.self, from: data)
completion(.success(user))
} catch {
completion(.failure(error))
}
}.resume()
}
// 使用
fetchUser(id: 1) { result in
switch result {
case .success(let user):
print("用户: \(user.name)")
case .failure(let error):
print("错误: \(error)")
}
}
async/await 方式
func fetchUser(id: Int) async throws -> User {
let url = URL(string: "https://api.example.com/users/\(id)")!
let (data, _) = try await URLSession.shared.data(from: url)
let user = try JSONDecoder().decode(User.self, from: data)
return user
}
// 使用
Task {
do {
let user = try await fetchUser(id: 1)
print("用户: \(user.name)")
} catch {
print("错误: \(error)")
}
}
示例 2: 串行异步操作
传统方式
func loadUserProfile(completion: @escaping (Result<UserProfile, Error>) -> Void) {
fetchUserID { result in
switch result {
case .success(let userID):
fetchUserData(userID: userID) { result in
switch result {
case .success(let userData):
fetchUserAvatar(userID: userID) { result in
switch result {
case .success(let avatar):
let profile = UserProfile(data: userData, avatar: avatar)
completion(.success(profile))
case .failure(let error):
completion(.failure(error))
}
}
case .failure(let error):
completion(.failure(error))
}
}
case .failure(let error):
completion(.failure(error))
}
}
}
async/await 方式
func loadUserProfile() async throws -> UserProfile {
let userID = try await fetchUserID()
let userData = try await fetchUserData(userID: userID)
let avatar = try await fetchUserAvatar(userID: userID)
return UserProfile(data: userData, avatar: avatar)
}
// 使用
Task {
do {
let profile = try await loadUserProfile()
print("用户资料加载完成")
} catch {
print("加载失败: \(error)")
}
}
示例 3: 并行异步操作
传统方式(使用 DispatchGroup)
func loadDashboardData(completion: @escaping (Result<DashboardData, Error>) -> Void) {
let group = DispatchGroup()
var userData: User?
var posts: [Post]?
var notifications: [Notification]?
var error: Error?
group.enter()
fetchUser { result in
switch result {
case .success(let user):
userData = user
case .failure(let err):
error = err
}
group.leave()
}
group.enter()
fetchPosts { result in
switch result {
case .success(let fetchedPosts):
posts = fetchedPosts
case .failure(let err):
error = err
}
group.leave()
}
group.enter()
fetchNotifications { result in
switch result {
case .success(let fetchedNotifications):
notifications = fetchedNotifications
case .failure(let err):
error = err
}
group.leave()
}
group.notify(queue: .main) {
if let error = error {
completion(.failure(error))
} else if let userData = userData, let posts = posts, let notifications = notifications {
let dashboard = DashboardData(user: userData, posts: posts, notifications: notifications)
completion(.success(dashboard))
} else {
completion(.failure(NetworkError.unknown))
}
}
}
async/await 方式
func loadDashboardData() async throws -> DashboardData {
async let user = fetchUser()
async let posts = fetchPosts()
async let notifications = fetchNotifications()
// 并行执行,等待所有结果
return try await DashboardData(
user: user,
posts: posts,
notifications: notifications
)
}
// 使用
Task {
do {
let dashboard = try await loadDashboardData()
print("仪表盘数据加载完成")
} catch {
print("加载失败: \(error)")
}
}
示例 4: 图片下载与缓存
actor ImageCache {
private var cache: [URL: UIImage] = [:]
func image(for url: URL) -> UIImage? {
return cache[url]
}
func store(_ image: UIImage, for url: URL) {
cache[url] = image
}
}
class ImageLoader {
private let cache = ImageCache()
func loadImage(from url: URL) async throws -> UIImage {
// 检查缓存
if let cachedImage = await cache.image(for: url) {
print("从缓存加载图片")
return cachedImage
}
// 下载图片
print("从网络下载图片")
let (data, _) = try await URLSession.shared.data(from: url)
guard let image = UIImage(data: data) else {
throw ImageError.invalidData
}
// 存入缓存
await cache.store(image, for: url)
return image
}
}
// 使用
let loader = ImageLoader()
Task {
do {
let url = URL(string: "https://example.com/image.jpg")!
let image = try await loader.loadImage(from: url)
print("图片加载完成: \(image.size)")
} catch {
print("加载失败: \(error)")
}
}
示例 5: 实时数据流处理
// 模拟实时数据流
struct StockPriceStream: AsyncSequence {
typealias Element = Double
let symbol: String
struct AsyncIterator: AsyncIteratorProtocol {
let symbol: String
mutating func next() async throws -> Double? {
// 模拟每秒更新一次股价
try await Task.sleep(nanoseconds: 1_000_000_000)
return Double.random(in: 100...200)
}
}
func makeAsyncIterator() -> AsyncIterator {
return AsyncIterator(symbol: symbol)
}
}
// 使用异步流
func monitorStockPrice(symbol: String) async throws {
let stream = StockPriceStream(symbol: symbol)
for try await price in stream {
print("\(symbol) 当前价格: $\(String(format: "%.2f", price))")
// 价格预警
if price > 180 {
print("⚠️ 价格过高警告!")
}
// 检查任务是否被取消
if Task.isCancelled {
print("停止监控")
break
}
}
}
// 启动监控
let task = Task {
try await monitorStockPrice(symbol: "AAPL")
}
// 5秒后取消
Task {
try await Task.sleep(nanoseconds: 5_000_000_000)
task.cancel()
}
示例 6: 文件批量处理
func processFiles(urls: [URL]) async throws -> [ProcessedFile] {
try await withThrowingTaskGroup(of: ProcessedFile.self) { group in
for url in urls {
group.addTask {
print("开始处理: \(url.lastPathComponent)")
let data = try await loadFile(from: url)
let processed = try await processData(data)
print("完成处理: \(url.lastPathComponent)")
return ProcessedFile(url: url, data: processed)
}
}
var results: [ProcessedFile] = []
for try await result in group {
results.append(result)
}
return results
}
}
func loadFile(from url: URL) async throws -> Data {
try await Task.sleep(nanoseconds: 1_000_000_000) // 模拟 I/O
return Data()
}
func processData(_ data: Data) async throws -> Data {
try await Task.sleep(nanoseconds: 500_000_000) // 模拟处理
return data
}
// 使用
Task {
let urls = [
URL(fileURLWithPath: "/path/file1.txt"),
URL(fileURLWithPath: "/path/file2.txt"),
URL(fileURLWithPath: "/path/file3.txt")
]
do {
let results = try await processFiles(urls: urls)
print("所有文件处理完成,共 \(results.count) 个")
} catch {
print("处理失败: \(error)")
}
}
与传统异步方案对比
1. Completion Handler vs async/await
| 特性 | Completion Handler | async/await |
|---|---|---|
| 代码可读性 | ❌ 嵌套深,难以阅读 | ✅ 线性流程,清晰直观 |
| 错误处理 | ❌ 每层都需要处理 | ✅ 统一的 try-catch |
| 内存管理 | ⚠️ 容易循环引用 | ✅ 自动管理 |
| 调试难度 | ❌ 堆栈追踪困难 | ✅ 堆栈清晰 |
| 编译器检查 | ⚠️ 检查有限 | ✅ 强类型检查 |
2. GCD vs Task
| 特性 | GCD | Task |
|---|---|---|
| 线程管理 | ❌ 手动管理队列 | ✅ 自动调度 |
| 取消操作 | ❌ 需要手动实现 | ✅ 内置取消机制 |
| 优先级 | ⚠️ 队列优先级 | ✅ 任务优先级 |
| 结构化并发 | ❌ 不支持 | ✅ 原生支持 |
| 数据竞争 | ❌ 容易出现 | ✅ Actor 保护 |
3. DispatchGroup vs TaskGroup
// DispatchGroup - 复杂且容易出错
func loadDataWithDispatchGroup(completion: @escaping ([Data]) -> Void) {
let group = DispatchGroup()
var results: [Data] = []
let lock = NSLock()
for i in 0..<5 {
group.enter()
fetchData(id: i) { data in
lock.lock()
results.append(data)
lock.unlock()
group.leave()
}
}
group.notify(queue: .main) {
completion(results)
}
}
// TaskGroup - 简洁且类型安全
func loadDataWithTaskGroup() async throws -> [Data] {
try await withThrowingTaskGroup(of: Data.self) { group in
for i in 0..<5 {
group.addTask {
try await fetchData(id: i)
}
}
var results: [Data] = []
for try await data in group {
results.append(data)
}
return results
}
}
优缺点分析
async/await 的优点
1. ✅ 代码可读性显著提升
// 清晰的顺序执行流程
func updateUserProfile() async throws {
let user = try await fetchUser()
let profile = try await fetchProfile(for: user)
let avatar = try await downloadAvatar(url: profile.avatarURL)
try await saveProfile(profile, avatar: avatar)
}
2. ✅ 错误处理统一
// 使用标准的 do-catch-throw 模式
do {
let result = try await riskyOperation()
print("成功: \(result)")
} catch {
print("失败: \(error)")
}
3. ✅ 自动内存管理
- 不需要使用
[weak self]或[unowned self] - 避免循环引用问题
- 编译器自动管理生命周期
4. ✅ 编译时安全检查
// 编译器会强制要求使用 await
func getData() async -> Data {
return await fetchData() // 必须使用 await,否则编译错误
}
5. ✅ 结构化并发
// 任务自动继承上下文和优先级
Task {
async let a = fetchA()
async let b = fetchB()
let results = try await [a, b]
}
6. ✅ 更好的调试体验
- 清晰的调用堆栈
- 断点调试更直观
- Instruments 工具支持更好
7. ✅ 线程安全
// Actor 提供自动的数据隔离
actor Counter {
private var value = 0
func increment() {
value += 1 // 自动线程安全
}
}
async/await 的缺点
1. ❌ 学习曲线
- 需要理解新的并发模型
- Actor、Task、AsyncSequence 等新概念
- 与旧代码混合使用时需要适配
2. ❌ 系统版本要求
- iOS 15.0+ / macOS 12.0+
- 无法在旧系统上使用
- 需要考虑向后兼容性
// 需要版本检查
if #available(iOS 15.0, *) {
Task {
await newAsyncFunction()
}
} else {
// 使用旧的异步方式
oldCallbackFunction { result in
// ...
}
}
3. ❌ 与旧代码集成需要桥接
// 需要手动桥接旧的 completion handler
func legacyFetch(completion: @escaping (Data?, Error?) -> Void) {
// 旧代码
}
// 桥接到 async/await
func modernFetch() async throws -> Data {
try await withCheckedThrowingContinuation { continuation in
legacyFetch { data, error in
if let error = error {
continuation.resume(throwing: error)
} else if let data = data {
continuation.resume(returning: data)
} else {
continuation.resume(throwing: NetworkError.unknown)
}
}
}
}
4. ❌ 性能开销
- Task 创建有轻微开销
- Actor 的隔离机制有性能成本
- 对于极简单的异步操作可能过度设计
5. ⚠️ 调试工具仍在完善
- Xcode 的并发调试工具还在改进
- 某些场景下的错误信息不够清晰
- 性能分析工具支持有限
6. ⚠️ 生态系统迁移中
- 许多第三方库仍在适配
- 部分框架同时提供两套 API
- 文档和最佳实践还在积累
最佳实践
1. 合理使用 async let
// ✅ 好的做法 - 并行执行独立任务
func loadDashboard() async throws -> Dashboard {
async let user = fetchUser()
async let posts = fetchPosts()
async let stats = fetchStats()
return try await Dashboard(user: user, posts: posts, stats: stats)
}
// ❌ 不好的做法 - 有依赖关系时不应使用 async let
func loadUserPosts() async throws -> [Post] {
async let user = fetchUser()
async let posts = fetchPosts(userId: user.id) // 错误!user 还未就绪
return try await posts
}
// ✅ 正确做法 - 串行执行有依赖的任务
func loadUserPosts() async throws -> [Post] {
let user = try await fetchUser()
let posts = try await fetchPosts(userId: user.id)
return posts
}
2. 使用 TaskGroup 处理动态数量的任务
// ✅ 使用 TaskGroup 处理可变数量的任务
func downloadImages(urls: [URL]) async throws -> [UIImage] {
try await withThrowingTaskGroup(of: (Int, UIImage).self) { group in
for (index, url) in urls.enumerated() {
group.addTask {
let image = try await downloadImage(from: url)
return (index, image)
}
}
var images = [UIImage?](repeating: nil, count: urls.count)
for try await (index, image) in group {
images[index] = image
}
return images.compactMap { $0 }
}
}
3. 正确处理任务取消
func longRunningTask() async throws {
for i in 0..<1000 {
// 定期检查取消状态
try Task.checkCancellation()
// 或者使用
if Task.isCancelled {
print("任务被取消,清理资源")
cleanup()
return
}
await processItem(i)
}
}
4. 使用 Actor 保护共享状态
// ✅ 使用 Actor 保护共享数据
actor DataStore {
private var cache: [String: Data] = [:]
func getData(key: String) -> Data? {
return cache[key]
}
func setData(_ data: Data, key: String) {
cache[key] = data
}
}
// ❌ 不要在多个任务间直接共享可变状态
class UnsafeDataStore {
var cache: [String: Data] = [:] // 数据竞争风险!
}
5. 避免在 Actor 中执行耗时同步操作
actor FileProcessor {
// ❌ 不好的做法 - 阻塞 Actor
func processFile(_ url: URL) -> Data {
let data = try! Data(contentsOf: url) // 同步 I/O,会阻塞
return process(data)
}
// ✅ 好的做法 - 使用异步操作
func processFile(_ url: URL) async throws -> Data {
let data = try await loadFile(url) // 异步 I/O
return process(data)
}
}
6. 合理设置任务优先级
// 用户交互 - 高优先级
Task(priority: .userInitiated) {
let result = await fetchCriticalData()
updateUI(with: result)
}
// 后台任务 - 低优先级
Task(priority: .background) {
await syncDataToServer()
}
7. 使用 MainActor 更新 UI
@MainActor
class ViewModel: ObservableObject {
@Published var data: [Item] = []
func loadData() async {
let items = await fetchItems()
// 自动在主线程执行
self.data = items
}
}
// 或者显式标记
func updateUI() async {
await MainActor.run {
label.text = "更新完成"
}
}
8. 桥接旧代码
// 将 completion handler 转换为 async/await
func fetchData() async throws -> Data {
try await withCheckedThrowingContinuation { continuation in
legacyFetchData { data, error in
if let error = error {
continuation.resume(throwing: error)
} else if let data = data {
continuation.resume(returning: data)
}
}
}
}
9. 避免过度并发
// ❌ 不好的做法 - 创建过多任务
for i in 0..<10000 {
Task {
await process(i)
}
}
// ✅ 好的做法 - 限制并发数量
func processBatch(_ items: [Item], maxConcurrency: Int = 5) async {
await withTaskGroup(of: Void.self) { group in
var iterator = items.makeIterator()
// 启动初始任务
for _ in 0..<maxConcurrency {
if let item = iterator.next() {
group.addTask { await process(item) }
}
}
// 当任务完成时,启动新任务
for await _ in group {
if let item = iterator.next() {
group.addTask { await process(item) }
}
}
}
}
10. 测试异步代码
import XCTest
class AsyncTests: XCTestCase {
func testAsyncFunction() async throws {
let result = try await fetchData()
XCTAssertNotNil(result)
}
func testTaskCancellation() async throws {
let task = Task {
try await longRunningOperation()
}
task.cancel()
do {
_ = try await task.value
XCTFail("应该抛出取消错误")
} catch is CancellationError {
// 预期的取消错误
}
}
}
总结
何时使用 async/await
✅ 推荐使用的场景:
- 新项目或新功能
- 复杂的异步流程
- 需要多个异步操作协调
- 需要更好的代码可维护性
- 目标系统版本支持(iOS 15+)
⚠️ 谨慎使用的场景:
- 需要支持旧系统版本
- 极简单的异步操作
- 性能极其敏感的场景
- 团队不熟悉新并发模型
关键要点
- async/await 让异步代码看起来像同步代码,大幅提升可读性
- Actor 提供了线程安全的数据隔离,避免数据竞争
- 结构化并发确保任务的生命周期管理更清晰
- 需要 iOS 15+,考虑向后兼容性
- 与旧代码集成需要桥接,但值得投入
- 编译器提供更强的类型检查,减少运行时错误
Swift 的新并发框架代表了现代异步编程的方向,虽然有学习成本和系统要求,但带来的代码质量和开发体验提升是值得的。对于新项目,强烈建议采用 async/await;对于现有项目,可以逐步迁移关键模块。
参考资源: