Swift Concurrency
问题
Swift 的 async/await、Task、Actor 分别解决什么问题?与 GCD 相比有什么优势?
答案
async/await
将异步代码写成同步风格,避免回调地狱:
// GCD 风格(回调嵌套)
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, error in
if let data = data {
completion(.success(data))
}
}.resume()
}
// Swift Concurrency 风格
func fetchData() async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
// 调用
Task {
let data = try await fetchData()
let user = try JSONDecoder().decode(User.self, from: data)
// 自动在调用者的上下文执行(如果是 @MainActor 就在主线程)
}
Task
// 非结构化 Task
Task {
let result = await fetchData()
}
// 可取消
let task = Task {
try await longRunningWork()
}
task.cancel()
// 分离 Task(不继承上下文)
Task.detached(priority: .background) {
await heavyComputation()
}
TaskGroup
func fetchAllImages(urls: [URL]) async -> [UIImage] {
await withTaskGroup(of: UIImage?.self) { group in
for url in urls {
group.addTask {
try? await downloadImage(url)
}
}
var images: [UIImage] = []
for await image in group {
if let image { images.append(image) }
}
return images
}
}
Actor — 线程安全
Actor 保证其内部状态同一时刻只有一个任务访问:
actor BankAccount {
private var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount // 无需加锁,Actor 保证串行访问
}
func withdraw(_ amount: Double) -> Bool {
guard balance >= amount else { return false }
balance -= amount
return true
}
}
// 使用(必须 await)
let account = BankAccount()
await account.deposit(100)
@MainActor — 主线程保证
@MainActor
class ViewModel: ObservableObject {
@Published var items: [Item] = []
func loadItems() async {
let data = await fetchFromNetwork() // 可能在后台线程
items = parse(data) // @MainActor 保证在主线程更新
}
}
Sendable
标记类型可以安全跨并发域传递:
struct UserData: Sendable { // 值类型默认 Sendable
let name: String
let age: Int
}
final class Config: Sendable { // class 需要 final + 不可变属性
let apiKey: String
init(apiKey: String) { self.apiKey = apiKey }
}
常见面试问题
Q1: Swift Concurrency 和 GCD 的主要区别?
答案:
| Swift Concurrency | GCD | |
|---|---|---|
| 语法 | async/await(同步风格) | 闭包回调 |
| 线程安全 | Actor 编译器保证 | 开发者自己加锁 |
| 取消 | 内置 Task.isCancelled | 不支持 |
| 线程管理 | 协作式线程池(不阻塞) | 可能线程爆炸 |
| 错误传播 | throws + try | 回调传递 Error |
Q2: Actor 和加锁(Lock)的区别?
答案:
- Lock:阻塞线程等待(同步),可能死锁
- Actor:挂起任务(async),不阻塞线程,编译器保证安全
- Actor 是更高级的抽象,消除了手动加锁的复杂性和风险
Q3: @MainActor 和 DispatchQueue.main.async 的关系?
答案:@MainActor 在底层使用 DispatchQueue.main,但提供编译时检查——如果非 @MainActor 上下文直接访问 @MainActor 属性,编译器会报错。比手动 dispatch 到主线程更安全。