Swift 并发编程怎么做?async/await 和 Actor 怎么用?
Swift 5.5 引入了 async/await 模型,用线性的代码写异步逻辑,告别回调地狱。async 标记异步函数,await 挂起等待结果,编译器保证不会阻塞线程。Task 是异步任务的执行容器,Actor 是线程安全的引用类型。
async/await 的核心优势:异步代码看起来和同步代码一样,从上到下顺序执行,错误处理也用正常的 try-catch。编译器在 await 挂起点自动让出线程,不浪费资源。
async let 实现结构化并发——多个异步操作并行执行,await 时一起等结果。TaskGroup 更灵活,适合动态数量的并行任务。
Actor 是带隔离的 class——同一时刻只有一个任务能访问 actor 的可变状态,编译器在编译期检查,不需要手动加锁。actor 的属性和方法默认隔离,外部访问必须用 await。
追问
Task 和 async let 有什么区别?
async let 是结构化并发——子任务的生命周期绑定在当前函数作用域,函数退出时子任务自动取消。Task 是非结构化并发——任务独立于作用域存在,需要手动管理取消。简单并行用 async let,复杂场景(动态添加任务、手动取消)用 Task 或 TaskGroup。
Actor 和 class 有什么区别?
Actor 和 class 都是引用类型,区别在并发安全:actor 的隔离属性和方法同一时刻只允许一个任务访问,class 没有这个保证。外部调用 actor 的方法必须 await(因为可能等锁),class 不需要。Actor 没有 deinit 问题(不像 class 需要担心循环引用),因为 actor 本身就是为并发设计的。
Sendable 是什么?什么时候需要?
Sendable 标记"可以安全跨并发域传递"的类型。值类型(struct/enum)如果所有属性都是 Sendable,自动遵循。class 默认不是 Sendable——引用类型跨域传递可能产生数据竞争。函数参数跨 actor 边界时,编译器要求类型必须是 Sendable。如果你确定某个 class 是线程安全的,可以手动标记 @unchecked Sendable。
MainActor 是什么?
MainActor 是标记"必须在主线程执行"的特殊 actor。UI 更新必须在主线程,用 @MainActor 标记的函数/类型自动在主线程调度。SwiftUI 的 View body 就是隐式 @MainActor 的。从后台任务切回主线程:await MainActor.run { ... },或者调用 @MainActor 标记的方法。
写段代码
swift// async/await func fetchUser(id: String) async throws -> User { let (data, _) = try await URLSession.shared.data(from: url) return try JSONDecoder().decode(User.self, from: data) } // async let 并行 func loadAll() async throws -> (User, [Post]) { async let user = fetchUser(id: "1") async let posts = fetchPosts() return try await (user, posts) } // Actor actor Counter { private var value = 0 func increment() -> Int { value += 1; return value } } let counter = Counter() Task { let v = await counter.increment() // 必须 await } // @MainActor @MainActor func updateUI() { label.text = "done" } Task { let data = await fetch() // 后台 await updateUI() // 切主线程 }