Swift Concurrency 入门指南

Swift 5.5 引入了一套全新的并发编程模型。告别了 GCD 的回调地狱和 OperationQueue 的繁琐配置,Swift Concurrency 带来了编译器级别的并发安全检查。本文面向已经有 Swift 基础但尚未深入使用新并发模型的开发者。

async/await:基础构件

async/await 是 Swift Concurrency 的基石。它让异步代码看起来像同步代码:

// 之前:GCD + completion handler
func fetchUser(id: String, completion: @escaping (Result<User, Error>) -> Void) {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    URLSession.shared.dataTask(with: url) { data, response, error in
        if let error = error {
            completion(.failure(error))
            return
        }
        let user = try! JSONDecoder().decode(User.self, from: data!)
        completion(.success(user))
    }.resume()
}

// 之后:async/await
func fetchUser(id: String) 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)
}

关键区别:

  • 函数签名从 completion: (Result) -> Void 变为 async throws -> User
  • 调用处从嵌套闭包变为 let user = try await fetchUser(id: "123")
  • 错误处理回归到 Swift 原生的 try/catch 机制

Task:创建异步上下文

Task 是异步工作的容器。你可以在同步代码中创建 Task 来进入异步上下文:

// 在 SwiftUI 的 .task {} modifier 中
struct ProfileView: View {
    @State private var user: User?

    var body: some View {
        VStack {
            if let user = user {
                Text(user.name)
            } else {
                ProgressView()
            }
        }
        .task {
            do {
                user = try await fetchUser(id: "123")
            } catch {
                print("Failed to load user: \(error)")
            }
        }
    }
}

Task 的几个重要特性:

  • 结构化并发:当父 Task 被取消时,所有子 Task 自动取消
  • 优先级继承:子 Task 继承父 Task 的优先级
  • Task.detached:创建一个与当前上下文无关的独立 Task(谨慎使用)

并发执行:async let 和 Task Group

当需要并行执行多个异步操作时,有两种方式:

// async let:适合固定数量的并行操作
func loadDashboard() async throws -> Dashboard {
    async let user = fetchUser(id: "123")
    async let notifications = fetchNotifications()
    async let feed = fetchTimeline()

    return try await Dashboard(
        user: user,
        notifications: notifications,
        feed: feed
    )
}

// TaskGroup:适合动态数量的并行操作
func loadFollowers(ids: [String]) async throws -> [User] {
    try await withThrowingTaskGroup(of: User.self) { group in
        for id in ids {
            group.addTask {
                try await fetchUser(id: id)
            }
        }

        var users: [User] = []
        for try await user in group {
            users.append(user)
        }
        return users
    }
}

async let 语法简洁,但要求你在编译期就知道需要并行执行什么。TaskGroup 更灵活,适合处理动态数量的并发任务。

Actor:数据竞争的终结者

Actor 是 Swift 对共享可变状态的解决方案。它保证同一时间只有一个 Task 可以访问 Actor 的内部状态:

actor DownloadCounter {
    private var count: Int = 0

    func increment() {
        count += 1
    }

    func current() -> Int {
        count
    }
}

let counter = DownloadCounter()
// 这些访问自动串行化
await counter.increment()
let total = await counter.current()

注意:所有对 Actor 隔离属性的访问都必须使用 await(除非在 Actor 内部或使用 nonisolated 标记)。编译器会在编译期阻止数据竞争,这是 Swift Concurrency 最强大的安全保证。

@MainActor:UI 线程安全

在 SwiftUI 中,所有 UI 更新必须在主线程上进行。@MainActor 让编译器自动保证这一点:

@MainActor
class ProfileViewModel: ObservableObject {
    @Published var user: User?

    func load() async {
        // fetchUser 可能在后台线程运行
        // 但赋值给 @Published 属性自动在主线程执行
        user = try? await APIService.fetchUser(id: "123")
    }
}

Sendable:跨并发域安全传递

Sendable 协议标记了可以安全地在并发域之间传递的类型。值类型(struct、enum)在成员都是 Sendable 时自动符合,但 class 需要手动实现。

// 自动符合 Sendable(所有成员是值类型)
struct User: Codable, Sendable {
    let id: String
    let name: String
}

// 需要手动标注
final class SharedCache: @unchecked Sendable {
    private let lock = NSLock()
    private var storage: [String: Data] = [:]

    func get(_ key: String) -> Data? {
        lock.lock(); defer { lock.unlock() }
        return storage[key]
    }
}

总结

Swift Concurrency 不仅仅是一组新的 API——它重新定义了我们编写异步代码的方式。编译器级别的安全检查、结构化的任务管理和简洁的语法,让并发编程从"小心别踩坑"变成了"编译器帮你看路"。

如果你还在使用 GCD 和 completion handler,我强烈建议你在下一个项目中尝试 Swift Concurrency。一旦用过,你就不想回去了。