ToolkitX
知识库工具箱

异步编程

async/await, Task, Actor

25min·进阶

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 入门

下一节