🚀 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
是处理异步数据加载、网络请求、后台任务的首选工具。