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。一旦用过,你就不想回去了。