Swift 并发编程:深入理解 async/await 原理与实战

本文最后更新于 2026年7月24日 早上

引言

在 Swift 5.5 中,Apple 引入了全新的并发模型,其中 async/await 无疑是最重要的特性之一。它彻底改变了我们编写异步代码的方式,让原本复杂的闭包嵌套(俗称”回调地狱”)变得像同步代码一样直观易读。

本文将深入浅出地讲解 async/await 的工作原理,并配合一个简单的网络请求 Demo,让你快速上手。


一、传统异步编程之痛

async/await 问世之前,我们通常使用 逃逸闭包(@escaping Closure)Combine 框架 来处理异步任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 传统的闭包回调方式
func fetchData(completion: @escaping (String) -> Void) {
DispatchQueue.global().async {
sleep(2) // 模拟网络请求
let result = "传统方式返回的数据"
DispatchQueue.main.async {
completion(result)
}
}
}

// 调用时会产生嵌套
fetchData { data in
print(data)
// 如果下一个请求依赖这个结果,嵌套会越来越深
}

这种方式的缺点显而易见:

  • 嵌套层次深:多个连续的异步操作会导致金字塔状的代码。
  • 错误处理复杂:需要在每个闭包中处理 Error
  • 逻辑割裂:代码的执行顺序不直观。

二、async/await 的核心原理

async/await 是建立在 协程(Coroutine) 概念之上的编译器级特性。它并不是多线程的简单封装,而是一种挂起与恢复的机制。

1. 状态机(State Machine)转换

当你在函数前面加上 async 关键字,编译器会将该函数编译成一个状态机。每个 await 调用都是一个挂起点(Suspension Point)。

  • 挂起(Suspend):当遇到 await 时,当前线程不会被阻塞,而是将函数的执行状态(局部变量、执行位置)保存起来,并将控制权交还给系统。
  • 恢复(Resume):当异步操作完成(无论是成功还是失败),系统会找到合适的线程,恢复之前保存的状态,并继续往下执行。

2. 结构化并发(Structured Concurrency)

Swift 强制要求 async 任务必须在一个 Task 中运行。Task 提供了执行上下文,并支持取消操作。这种结构保证了任务的生命周期与父任务绑定,防止资源泄漏。

3. 线程调度

async/await 本身不绑定特定线程。在挂起期间,线程可以去做其他工作;恢复时,任务可能会被调度到任意一个可用线程上(除非使用 @MainActor 强制指定主线程)。


三、实战 Demo:简易网络请求

我们用一个真实的场景:请求一个公开的 API(如 https://api.github.com/users/apple),获取用户信息并打印。

步骤 1:定义数据模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Foundation

// 遵循 Decodable 协议,用于自动解析 JSON
struct GitHubUser: Decodable {
let login: String
let name: String?
let location: String?
let publicRepos: Int

// 如果 JSON 键名与属性名不一致,使用 CodingKeys
enum CodingKeys: String, CodingKey {
case login
case name
case location
case publicRepos = "public_repos"
}
}

步骤 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
27
28
29
30
31
32
// 定义一个枚举用于错误处理
enum NetworkError: Error {
case invalidURL
case requestFailed(Error)
case invalidResponse
case decodingFailed(Error)
}

// 核心异步函数
func fetchUserInfo(username: String) async throws -> GitHubUser {
// 1. 构造 URL
guard let url = URL(string: "https://api.github.com/users/\(username)") else {
throw NetworkError.invalidURL
}

// 2. 发起网络请求(挂起点1)
let (data, response) = try await URLSession.shared.data(from: url)

// 3. 检查 HTTP 响应状态
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw NetworkError.invalidResponse
}

// 4. 解析 JSON
let decoder = JSONDecoder()
guard let user = try? decoder.decode(GitHubUser.self, from: data) else {
throw NetworkError.decodingFailed(NSError(domain: "Decode", code: 0))
}

return user
}

注意URLSession.shared.data(from:) 是 Apple 提供的原生 async 方法。

步骤 3:主流程调用与打印

由于 Swift 顶层不能直接调用 async 函数,我们需要在 Task 块中执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Task {
do {
print("开始请求 GitHub 数据...")
// 调用异步函数(挂起点)
let user = try await fetchUserInfo(username: "apple")

// 打印结果
print("""
==========================
用户名: \(user.login)
姓名: \(user.name ?? "无")
位置: \(user.location ?? "无")
公开仓库数: \(user.publicRepos)
==========================
""")
} catch {
print("请求失败:\(error.localizedDescription)")
}
}

// 如果在 Playground 中运行,需要开启无限执行
// PlaygroundPage.current.needsIndefiniteExecution = true

四、运行结果与代码分析

当你运行上述代码时,控制台会立即打印 开始请求...,然后等待约 1-2 秒(取决于网络),最后输出用户信息。

关键点解析:

  1. **try await**:try 表示该方法可能抛出错误,await 表示此处可能挂起。
  2. 非阻塞:在 await 期间,线程并没有被占用,它可以去处理其他任务(例如 UI 渲染)。
  3. 错误传播throws 关键字让错误可以像同步代码一样向上冒泡,无需在闭包中层层传递。

五、进阶:关于 Task 与 MainActor

1. 什么是 Task?

Task 是一个异步操作的执行单元。它从当前上下文继承优先级和取消状态。

  • Task.detached:创建一个独立的任务,不继承父任务的上下文(较少使用)。
  • 默认的 Task {} 会继承当前 Actor 的上下文。

2. 回到主线程更新 UI

如果我们在一个后台任务中获取数据,需要更新 UI,必须回到主线程。Swift 提供了 @MainActor 来标记:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 方式一:使用 MainActor.run
Task {
let user = try await fetchUserInfo(username: "apple")
await MainActor.run {
// 更新 UI 代码
print("UI 更新:\(user.name ?? "")")
}
}

// 方式二:将函数声明在主 Actor 上
@MainActor
func updateUI(with user: GitHubUser) {
// 这里是主线程
}

六、总结

特性 闭包回调 async/await
代码可读性 差(金字塔) 优秀(线性)
错误处理 复杂,需传递 简单,原生 try/catch
线程管理 手动切换 自动挂起/恢复,调度优化
调试体验 难(断点难定位) 好(类似同步函数)

async/await 的本质是编译器将异步代码转换为一个高效的状态机,它极大地提升了 Swift 开发者的生产力。掌握它,是迈向现代 Swift 开发的关键一步。


Swift 并发编程:深入理解 async/await 原理与实战
https://wttch96.github.io/post/swift/swift-async-await.html
作者
Wttch
发布于
2026年7月21日
更新于
2026年7月24日
许可协议