跳到主要内容

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 ConcurrencyGCD
语法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 到主线程更安全。

相关链接