深入理解 Swift 中的 async/await:告别回调地狱,拥抱结构化并发

原文:Async await in Swift explained with code examples

Swift 5.5 在 WWDC 2021 中引入了 async/await,随后在 Swift 6 中进一步完善,成为现代 iOS 开发中处理并发的核心工具。它不仅让异步代码更易读写,还彻底改变了我们组织并发任务的方式。

什么是 async?

async 是一个方法修饰符,表示该方法是异步执行的,即不会阻塞当前线程,而是挂起等待结果。

✅ 示例:定义一个 async 方法

swift 复制代码
func fetchImages() async throws -> [UIImage] {
    // 模拟网络请求
    let data = try await URLSession.shared.data(from: URL(string: "https://example.com/images")!).0
    return try JSONDecoder().decode([UIImage].self, from: data)
}
  • async 表示异步执行;
  • throws 表示可能抛出错误;
  • 返回值是 [UIImage]
  • 调用时需要用 await 等待结果。

什么是 await?

await 是调用 async 方法时必须使用的关键字,表示"等待异步结果"。

✅ 示例:使用 await 调用 async 方法

swift 复制代码
do {
    let images = try await fetchImages()
    print("成功获取 \(images.count) 张图片")
} catch {
    print("获取图片失败:\(error)")
}
  • 使用 try await 等待异步结果;
  • 错误用 catch 捕获;
  • 代码顺序执行,逻辑清晰。

async/await 如何替代回调地狱?

在 async/await 出现之前,异步操作通常使用回调闭包,这会导致回调地狱(Callback Hell):

❌ 旧写法:嵌套回调

swift 复制代码
fetchImages { result in
    switch result {
    case .success(let images):
        resizeImages(images) { result in
            switch result {
            case .success(let resized):
                print("处理完成:\(resized.count) 张图片")
            case .failure(let error):
                print("处理失败:\(error)")
            }
        }
    case .failure(let error):
        print("获取失败:\(error)")
    }
}

✅ 新写法:线性结构

swift 复制代码
do {
    let images = try await fetchImages()
    let resizedImages = try await resizeImages(images)
    print("处理完成:\(resizedImages.count) 张图片")
} catch {
    print("处理失败:\(error)")
}
  • 没有嵌套;
  • 顺序清晰;
  • 更易于维护和测试。

在非并发环境中调用 async 方法

如果你尝试在同步函数中直接调用 async 方法,会报错:

'async' call in a function that does not support concurrency

✅ 解决方案:使用 Task

swift 复制代码
final class ContentViewModel: ObservableObject {
    @Published var images: [UIImage] = []

    func fetchData() {
        Task { @MainActor in
            do {
                self.images = try await fetchImages()
            } catch {
                print("获取失败:\(error)")
            }
        }
    }
}
  • Task {} 创建一个新的异步上下文;
  • @MainActor 保证 UI 更新在主线程;
  • 适用于 SwiftUI 或 UIKit。

如何在旧项目中逐步迁移?

Xcode 提供了三种自动重构方式,帮助你从旧回调方式迁移到 async/await:

✅ 方式一:Convert Function to Async

直接替换旧方法,不保留旧实现:

swift 复制代码
// 旧
func fetchImages(completion: @escaping (Result<[UIImage], Error>) -> Void)

// 新
func fetchImages() async throws -> [UIImage]

✅ 方式二:Add Async Alternative

保留旧方法,并添加新 async 方法,使用 @available 标记:

swift 复制代码
@available(*, deprecated, renamed: "fetchImages()")
func fetchImages(completion: @escaping (Result<[UIImage], Error>) -> Void) {
    Task {
        do {
            let result = try await fetchImages()
            completion(.success(result))
        } catch {
            completion(.failure(error))
        }
    }
}

func fetchImages() async throws -> [UIImage] {
    // 新实现
}
  • 旧方法调用会提示警告;
  • 支持逐步迁移;
  • 不破坏现有代码。

✅ 方式三:Add Async Wrapper

使用 withCheckedThrowingContinuation 包装旧方法:

swift 复制代码
func fetchImages() async throws -> [UIImage] {
    try await withCheckedThrowingContinuation { continuation in
        fetchImages { result in
            continuation.resume(with: result)
        }
    }
}
  • 无需改动旧实现;
  • 适合第三方库或无法修改的代码。

async/await 会取代 Result 枚举吗?

虽然 async/await 让 Result 枚举看起来不再必要,但它不会立即消失。很多老代码和第三方库仍在使用 Result,但未来可能会逐步弃用。

迁移建议:先 async,再 Swift 6

  • Swift 6 引入了更强的并发安全检查;
  • 建议先迁移到 async/await,再升级到 Swift 6;
  • 使用 @preconcurrency@Sendable 等工具逐步迁移。

总结:async/await 带来的改变

特性 回调方式 async/await
可读性 差(嵌套) 好(线性)
错误处理 手动 Result try/catch
并发控制 手动管理 结构化
测试难度
与 SwiftUI 集成 复杂 自然
相关推荐
progalchemist14 小时前
Quick SwiftObjective-C测试框架入门教程
开发语言·其他·objective-c·swift
HarderCoder16 小时前
Swift 闭包(Closure)从入门到深入:语法、捕获与实战
swift
HarderCoder1 天前
Swift 集合类型详解(三):自定义集合、持久化结构与 ORM 共舞
swift
HarderCoder1 天前
Swift 集合类型详解(一):Array、Set、Dictionary 全貌与选型思路
swift
HarderCoder1 天前
Swift 集合类型详解(二):自定义 Hashable、值语义与性能陷阱
swift
东坡肘子1 天前
Sora 2:好模型,但未必是好生意 | 肘子的 Swift 周报 #0105
人工智能·swiftui·swift
HarderCoder2 天前
Swift 6 并发深渊:@unchecked Sendable 与“隐式 MainActor”如何合谋杀死你的 App
swiftui·swift
HarderCoder2 天前
告别 UIKit 生命周期:SwiftUI 视图一生全解析——从 init 到 deinit 的“隐秘角落”
swiftui·swift
HarderCoder2 天前
Swift 中的基本运算符:从加减乘除到逻辑与或非
ios·swift
HarderCoder2 天前
Swift 中“特性开关”实战笔记——用编译条件+EnvironmentValues优雅管理Debug/TestFlight/AppStore三环境
ios·swift