01. async/await 入门
Swift 5.5 引入了 async/await,让异步代码写得像同步代码一样直。async 标记函数是异步的,await 表示挂起点等待异步操作完成。之前写网络请求要用回调闭包嵌套好几层,现在一行 await 就搞定了。异步函数会跑在 Swift 的协作式线程池上。
swift
// 异步函数
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)
return try JSONDecoder().decode(User.self, from: data)
}
// 调用异步函数
Task {
do {
let user = try await fetchUser(id: 1)
print(user.name)
} catch {
print("获取失败: \(error)")
}
}async 函数只能从异步上下文中调用:要么在另一个 async 函数里,要么用 Task 启动。
02. Task 和 TaskGroup
Task 代表一个异步工作单元。Task {} 创建顶级任务,Task.detached {} 创建脱离当前上下文的任务。TaskGroup 可以并行执行多个子任务,等待所有结果返回。withTaskGroup 创建任务组,group.addTask 添加子任务,for await 循环收集结果。
swift
func loadAllUsers(ids: [Int]) async -> [User] {
await withTaskGroup(of: User.self) { group in
for id in ids {
group.addTask {
try? await fetchUser(id: id)
}
}
var users: [User] = []
for await user in group {
users.append(user)
}
return users
}
}
// 取消任务
let task = Task {
await doLongWork()
}
task.cancel() // 请求取消
// 检查取消
func doLongWork() async throws {
try Task.checkCancellation() // 如果已取消就抛异常
for i in 1...100 {
if Task.isCancelled { return }
// 做事情...
}
}长时间运行的任务应该定期检查 Task.isCancelled,及时响应取消请求。
03. Actor 数据安全
Actor 是 Swift 的并发安全类型,内部的数据一次只能被一个任务访问,不会产生数据竞争。用 actor 代替 class 就能获得线程安全保护。访问 actor 的方法和属性需要 await,因为可能在等别的任务先访问完。@MainActor 确保代码在主线程执行,适合 UI 更新。
swift
actor Counter {
private var value = 0
func increment() -> Int {
value += 1
return value
}
func reset() {
value = 0
}
}
let counter = Counter()
Task {
let v1 = await counter.increment()
let v2 = await counter.increment()
print("\(v1), \(v2)") // 肯定是 1, 2,不会乱了
}
// @MainActor 确保在主线
@MainActor
class ViewModel: ObservableObject {
@Published var users: [User] = []
func load() async {
do {
users = try await fetchUsers()
} catch { }
}
}Actor 内部的方法互相调用不需要 await,但从外部访问 actor 的任何东西都要 await。
04. continuation 桥接回调
很多老的 API 还是回调闭包风格的,需要桥接到 async/await 世界。withCheckedContinuation 和 withCheckedThrowingContinuation 就是做这个桥接的。函数会挂起,等你调用 continuation.resume() 返回结果或 continuation.resume(throwing:) 抛出错误。
swift
// 老的回调 API
func oldStyleLogin(username: String, completion: @escaping (Result<String, Error>) -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
completion(.success("token-xxx"))
}
}
// 桥接成 async
func login(username: String) async throws -> String {
try await withCheckedThrowingContinuation { continuation in
oldStyleLogin(username: username) { result in
switch result {
case .success(let token):
continuation.resume(returning: token)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
// 使用
Task {
let token = try await login(username: "test")
print(token)
}continuation 必须且只能 resume 一次,多了或少了一次都会导致运行时错误。
05. Sendable 和安全数据传递
Sendable 协议标记可以安全在并发域之间传递的类型。值类型(struct、enum)和不可变类自动是 Sendable。有可变状态的类需要手动标注 @unchecked Sendable 并自己保证线程安全。编译器会检查你跨 actor 传递的数据是否满足 Sendable。
swift
// 自动 Sendable
struct Message: Sendable {
let id: Int
let content: String
}
// 不可变类是 Sendable
final class Config: Sendable {
let apiKey: String
init(apiKey: String) { self.apiKey = apiKey }
}
// 需要手动保证的
final class Cache: @unchecked Sendable {
private let lock = NSLock()
private var storage: [String: Any] = [:]
func set(_ value: Any, forKey key: String) {
lock.lock()
storage[key] = value
lock.unlock()
}
}尽量用值类型(struct/enum)来传递数据,它们天然的线程安全且自动满足 Sendable。
知识测验
第 1/4 题正确 0
Swift async/await 的核心优势是什么?
下一节
下一节 SwiftUI 入门