🚀 Swift 中 async/await 的完整指南#
async/await 是 Swift 5.5 引入的异步编程模型,基于 Swift Concurrency,旨在替代传统的回调闭包(Closures)和 GCD(Grand Central Dispatch)写法。它可以让异步代码像同步代码一样简洁、可读,极大地简化了处理异步任务的复杂性。
🧩 1. async/await 的基本概念#
async:用于声明异步函数,表示该函数内部可能会有异步操作(例如网络请求、I/O 等)。await:用于调用异步函数,表示等待异步任务完成,获取其返回结果。
✅ 示例:基本使用#
import Foundation
// 定义异步函数
func fetchData() async -> String {
// 模拟耗时操作
try? await Task.sleep(nanoseconds: 2 * 1_000_000_000) // 等待 2 秒
return "Hello from async/await!"
}
// 调用异步函数
Task {
let result = await fetchData()
print(result)
}解释:#
async声明fetchData为异步函数。await表示等待异步任务完成,挂起当前任务,直至获取结果。Task {}用于在非异步上下文中调用异步函数(例如在main.swift或 SwiftUI 中的Button)。
⚡ 2. 异步函数的定义与调用#
(1) 异步函数的定义#
func loadUserData() async -> String {
return "User Data Loaded"
}async放在->前,表示函数为异步函数。- 支持任何返回类型,甚至可以结合
Result或Optional。
(2) 异步函数的调用#
✅ 在异步上下文中调用#
func performTask() async {
let data = await loadUserData()
print(data)
}✅ 在同步上下文中调用(使用 Task)#
Task {
let data = await loadUserData()
print(data)
}- 如果你在
Button、onAppear等事件中调用异步函数,需要使用Task包裹。
🌍 3. 异步网络请求示例#
import Foundation
func fetchPost() async throws -> String {
let url = URL(string: "https://jsonplaceholder.typicode.com/posts/1")!
let (data, _) = try await URLSession.shared.data(from: url)
return String(data: data, encoding: .utf8) ?? "No Data"
}
Task {
do {
let post = try await fetchPost()
print(post)
} catch {
print("Error: \(error)")
}
}关键点:#
try await:表示异步操作可能抛出错误,需使用do-catch捕获。URLSession.shared.data(from:)是支持async/await的异步方法。
🧵 4. 结构化并发(Structured Concurrency)#
✅ (1) 并行执行多个异步任务#
func fetchUser() async -> String {
try? await Task.sleep(nanoseconds: 1_000_000_000)
return "User Data"
}
func fetchPosts() async -> String {
try? await Task.sleep(nanoseconds: 2_000_000_000)
return "Posts Data"
}
Task {
async let user = fetchUser()
async let posts = fetchPosts()
let result = await "\(await user), \(await posts)"
print(result)
}解释:#
async let:并行启动多个异步任务,提升性能。await:等待任务完成,按需获取结果。
✅ (2) 使用 TaskGroup 执行并发任务#
func fetchItems() async -> [String] {
await withTaskGroup(of: String.self) { group in
for i in 1...3 {
group.addTask {
try? await Task.sleep(nanoseconds: UInt64(i) * 1_000_000_000)
return "Item \(i)"
}
}
var results = [String]()
for await result in group {
results.append(result)
}
return results
}
}
Task {
let items = await fetchItems()
print(items)
}withTaskGroup:创建任务组,管理多个异步任务。for await:异步遍历任务组的结果,按任务完成的顺序返回。
❌ 5. 处理异步错误#
(1) 使用 try await 和 do-catch#
func riskyTask() async throws -> String {
let success = Bool.random()
if success {
return "Success!"
} else {
throw NSError(domain: "TaskError", code: 1, userInfo: nil)
}
}
Task {
do {
let result = try await riskyTask()
print(result)
} catch {
print("Error occurred: \(error.localizedDescription)")
}
}try await:用于可能抛出错误的异步函数。do-catch:捕获并处理错误。
⚙️ 6. MainActor 确保主线程更新 UI#
在 SwiftUI 中,UI 更新必须在主线程上完成,@MainActor 可确保这一点:
@MainActor
func updateUI(_ text: String) {
print("UI updated with: \(text)")
}
Task {
let data = await fetchUser()
await updateUI(data) // 确保 UI 更新在主线程上
}@MainActor:将函数标记为主线程执行,确保线程安全。
🚀 7. async/await vs GCD vs Combine#
| 特性 | async/await | GCD(DispatchQueue) | Combine |
|---|---|---|---|
| 代码可读性 | ✅ 简洁、接近同步代码 | ❌ 回调嵌套复杂 | ⚠️ 流式编程,需适应思维模型 |
| 错误处理 | ✅ 直接支持 try-catch | ❌ 需要手动处理错误 | ✅ catch 操作符处理错误 |
| 并发性能 | ✅ 支持结构化并发,自动优化线程调度 | ⚠️ 需要手动管理队列 | ⚠️ 多线程管理需额外关注 |
| UI 更新支持 | ✅ 搭配 @MainActor 易于主线程更新 | ⚠️ 需明确指定 DispatchQueue.main.async | ⚠️ 需使用 .receive(on:) 指定主线程 |
| 学习曲线 | 🚀 容易上手 | 🧠 易于初学者理解 | 📊 较高,适合响应式编程 |
🎯 8. 最佳实践#
- 异步任务管理:在 SwiftUI 中,使用
Task和@MainActor确保 UI 安全更新。 - 避免阻塞主线程:即使在异步函数中,也要避免使用
sleep()等阻塞操作,推荐使用Task.sleep()。 - 错误处理:始终使用
try await和do-catch,确保错误被妥善处理。 - 结构化并发:当需要并行处理多个任务时,优先考虑
async let或TaskGroup,提高性能。 - 资源释放:在适当时机取消不必要的异步任务,避免内存泄漏或不必要的资源占用。
🔑 总结#
async/await提供了简洁、高效的异步编程模型,极大地提高了代码可读性和可维护性。- 它与 Swift 的结构化并发模型深度集成,支持错误处理、任务取消和主线程更新等高级功能。
- 在 SwiftUI 中,
async/await是处理异步数据加载、网络请求、后台任务的首选工具。