Swift Concurrency
问题
Swift 的现代并发模型是什么?async/await、Actor、Structured Concurrency 各自解决什么问题?
答案
回调地狱 → async/await
传统回调方式的问题:
// ❌ 回调嵌套(Callback Hell)
func loadUserProfile(userId: String) {
fetchUser(userId) { user in
guard let user = user else { return }
fetchAvatar(user.avatarURL) { image in
guard let image = image else { return }
fetchPosts(userId) { posts in
// 三层嵌套,难以维护
updateUI(user: user, avatar: image, posts: posts ?? [])
}
}
}
}
// ✅ async/await:线性代码
func loadUserProfile(userId: String) async throws {
let user = try await fetchUser(userId)
let avatar = try await fetchAvatar(user.avatarURL)
let posts = try await fetchPosts(userId)
await updateUI(user: user, avatar: avatar, posts: posts)
}
async/await 基础
// 声明异步函数
func fetchData(from url: URL) async throws -> Data {
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw NetworkError.serverError
}
return data
}
// 调用:必须在异步上下文中
Task {
do {
let data = try await fetchData(from: someURL)
print("Got \(data.count) bytes")
} catch {
print("Error: \(error)")
}
}
Task
Task 是异步工作的基本单元:
// 非结构化 Task:手动创建
let task = Task {
let result = try await fetchData()
return result
}
// 获取结果
let data = try await task.value
// 取消 Task
task.cancel()
// 检查是否被取消
Task {
for i in 0..<1000 {
try Task.checkCancellation() // 被取消时抛出 CancellationError
await processItem(i)
}
}
// 分离式 Task:不继承父上下文
Task.detached(priority: .background) {
await heavyComputation()
}
TaskGroup(并发执行)
func fetchAllImages(urls: [URL]) async throws -> [UIImage] {
try await withThrowingTaskGroup(of: UIImage.self) { group in
for url in urls {
group.addTask {
let (data, _) = try await URLSession.shared.data(from: url)
guard let image = UIImage(data: data) else {
throw ImageError.invalidData
}
return image
}
}
var images: [UIImage] = []
for try await image in group {
images.append(image)
}
return images
}
}
Actor
Actor 是保护共享可变状态的并发安全类型:
actor BankAccount {
private var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
func withdraw(_ amount: Double) throws -> Double {
guard balance >= amount else {
throw BankError.insufficientFunds
}
balance -= amount
return amount
}
// 只读属性也需要 await 访问(Actor 隔离)
var currentBalance: Double { balance }
}
let account = BankAccount()
await account.deposit(100) // 必须 await
let balance = await account.currentBalance // 必须 await
Actor 隔离
Actor 的所有属性和方法默认被隔离。外部访问必须通过 await,Actor 内部同步调用不需要 await。同一时间只有一个 Task 能访问 Actor 的状态(类似串行队列),从而保证线程安全。
@MainActor
标记必须在主线程执行的代码:
// 标记整个类
@MainActor
class ViewModel: ObservableObject {
@Published var items: [Item] = []
func loadItems() async {
let data = await fetchItems() // 可以在后台执行
items = data // 回到主线程更新 UI
}
}
// 标记单个函数
@MainActor
func updateUI() {
label.text = "Updated"
}
// 手动切换到主线程
Task { @MainActor in
label.text = "Done"
}
Sendable 协议
Sendable 标记可以安全在并发上下文间传递的类型:
// 值类型自动 Sendable
struct User: Sendable {
let name: String
let age: Int
}
// class 需要手动声明(需满足条件)
final class Config: Sendable {
let apiKey: String // 所有属性必须是 let + Sendable
init(apiKey: String) { self.apiKey = apiKey }
}
// @Sendable 闭包
Task.detached { @Sendable in
// 闭包内捕获的所有值必须是 Sendable
}
AsyncSequence
异步版本的 Sequence:
// URLSession 的 bytes 返回 AsyncSequence
let (bytes, _) = try await URLSession.shared.bytes(from: url)
for try await byte in bytes {
buffer.append(byte)
}
// AsyncStream:自定义异步序列
let stream = AsyncStream<Int> { continuation in
for i in 0..<10 {
continuation.yield(i)
}
continuation.finish()
}
for await value in stream {
print(value)
}
Continuation:桥接回调到 async
将传统回调 API 转换为 async:
func fetchLocation() async throws -> CLLocation {
try await withCheckedThrowingContinuation { continuation in
locationManager.requestLocation { location, error in
if let error = error {
continuation.resume(throwing: error)
} else if let location = location {
continuation.resume(returning: location)
}
}
}
}
关键规则
每个 continuation 必须恰好 resume 一次。不 resume 会导致 Task 永久挂起(内存泄漏),多次 resume 是未定义行为。
常见面试问题
Q1: async/await 和 GCD 有什么区别?
答案:
| async/await | GCD | |
|---|---|---|
| 语法 | 线性、同步风格 | 回调/闭包嵌套 |
| 错误处理 | try await 配合 do-catch | 回调中手动处理 |
| 取消 | Task.cancel() + Task.checkCancellation() | 手动标志位 |
| 线程管理 | 编译器 + runtime 管理 | 手动管理队列 |
| 编译器检查 | 数据竞争检查(strict concurrency) | 无编译时检查 |
Q2: Actor 和 class + DispatchQueue 有什么区别?
答案:
- Actor:语言级别的并发安全机制,编译器保证隔离,无需手动加锁
- class + 串行队列:运行时保护,开发者手动保证,容易遗漏
Actor 的优势:编译器会在你忘记 await 时报错,class + queue 方式只能靠自觉。
Q3: @MainActor 和 DispatchQueue.main.async 的区别?
答案:
@MainActor 是编译器保证——标记的函数/类只能在主线程访问,非主线程调用会被编译器要求加 await。DispatchQueue.main.async 是运行时切换到主线程,如果忘记用不会有编译错误,可能导致 UI 从后台线程更新。
Q4: Structured Concurrency 是什么?
答案:
结构化并发意味着子 Task 的生命周期受限于父 Task:
- 父 Task 取消 → 所有子 Task 自动取消
- 父 Task 等待所有子 Task 完成才返回
- 错误自动向上传播
TaskGroup 和 async let 是结构化并发,而 Task { } 和 Task.detached 是非结构化的。
Q5: async let 和 TaskGroup 的区别?
答案:
// async let:已知固定数量的并发任务
async let user = fetchUser()
async let posts = fetchPosts()
let (u, p) = try await (user, posts) // 并行获取
// TaskGroup:动态数量的并发任务
try await withTaskGroup(of: Image.self) { group in
for url in urls { // 数量不固定
group.addTask { try await fetchImage(url) }
}
}