| 分类 ios  | 标签 iOS  async/await 

title

引言

Swift 5.5 引入的 async/await 语法彻底简化了异步编程。开发者可以用同步风格的代码编写异步逻辑,无需嵌套回调和复杂的错误处理。配合 Actor 模型和结构化并发,Swift 并发模型提供了数据竞争安全、可取消、可优先级的并发能力。本文将深入探讨 async/await 的基础用法、Task 与结构化并发、Actor 数据隔离、MainActor 的使用,以及与传统回调的互操作。

为什么需要 async/await

传统的异步编程依赖 completion handler 和闭包,多层嵌套会导致”回调地狱”,错误处理分散且容易遗漏。async/await 将异步操作抽象为可挂起、可恢复的函数,代码呈线性结构,错误通过 throwstry 统一处理,可读性和可维护性大幅提升。此外,Swift 并发模型在编译器层面检查数据竞争,配合 Actor 可在编写并发代码时获得更强的安全保障。

基础用法

async 函数使用 async 关键字标记,内部可调用其他 async 函数或使用 await 等待异步操作。await 表示挂起点:当等待的异步操作未完成时,当前任务会挂起,线程可去执行其他工作;操作完成后任务恢复执行。从同步上下文调用 async 函数必须使用 Task { } 创建异步任务。

func fetchUser(id: Int) async throws -> User {
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

// 调用
Task {
    do {
        let user = try await fetchUser(id: 1)
        print(user.name)
    } catch {
        print("Error: \(error)")
    }
}

URLSession.shared.data(from:) 在 iOS 15+ 提供了 async 版本,直接 try await 即可,无需 completion handler。错误通过 throws 传播,调用方用 do-catch 处理。

并发执行

当多个异步操作相互独立时,可使用 async let 并发启动,再在需要时 await 结果。这样多个操作会并行执行,总耗时接近最慢的那个,而非顺序执行的总和。

async let user = fetchUser(id: 1)
async let posts = fetchPosts(userId: 1)

let (userResult, postsResult) = try await (user, posts)

async let 创建子任务,两个 fetch 同时进行;try await (user, posts) 会等待两者都完成,若有任一抛出错误则整体抛出。

Actor 数据隔离

Actor 是 Swift 并发中用于共享可变状态的类型。Actor 隔离其内部状态,所有对状态的访问必须通过 actor 的方法,且方法默认是异步的(从外部调用需 await)。这保证了同一时刻只有一个任务能访问 actor 内部数据,从而避免数据竞争。

actor Counter {
    private var value = 0
    
    func increment() {
        value += 1
    }
    
    func getValue() -> Int {
        return value
    }
}

let counter = Counter()
Task {
    await counter.increment()
    print(await counter.getValue())
}

从非 actor 隔离的上下文(如 Task 闭包内)访问 actor 的方法或属性必须加 await,因为调用可能会挂起以等待 actor 空闲。

与 MainActor

UI 更新必须在主线程执行。@MainActor 将类型或方法标记为在主线程执行,在 async 函数中调用被 @MainActor 标记的代码时,会自动切换到主线程。ViewModel 若需要更新 UI,可标记为 @MainActor class,其方法内的赋值会安全地在主线程执行。

@MainActor
class ViewModel: ObservableObject {
    @Published var items: [Item] = []
    
    func loadItems() async {
        let fetched = await apiService.fetchItems()
        items = fetched  // 已在主线程
    }
}

loadItems 是 async 的,可能在后台执行,但 items = fetched 会切换到主线程,因为 ViewModel@MainActor 隔离的。

任务取消与 TaskGroup

Task 支持取消:Task.isCancelled 可检查,Task.checkCancellation() 在取消时抛出 CancellationErrorTaskGroup 用于动态数量的并发子任务,适合批量处理。合理利用取消和 TaskGroup 可以构建可响应、高效的并发逻辑。

总结

async/await 是 Swift 并发的现代方案,能够显著提升异步代码的可读性和可维护性。掌握 async/await、Task、Actor 和 MainActor 是编写高质量并发代码的基础。在新项目中优先使用 async/await,老项目可逐步迁移,并与 Combine 等现有方案配合使用。


上一篇     下一篇