跳到主要内容

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 的所有属性和方法默认被隔离。外部访问必须通过 awaitActor 内部同步调用不需要 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/awaitGCD
语法线性、同步风格回调/闭包嵌套
错误处理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 是编译器保证——标记的函数/类只能在主线程访问,非主线程调用会被编译器要求加 awaitDispatchQueue.main.async 是运行时切换到主线程,如果忘记用不会有编译错误,可能导致 UI 从后台线程更新。

Q4: Structured Concurrency 是什么?

答案

结构化并发意味着子 Task 的生命周期受限于父 Task

  • 父 Task 取消 → 所有子 Task 自动取消
  • 父 Task 等待所有子 Task 完成才返回
  • 错误自动向上传播

TaskGroupasync 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) }
}
}

相关链接