← 返回文章

Swift · Concurrency

Swift Concurrency 在业务层的边界

async/await 最容易被误用的地方,不是语法,而是边界。业务层如果随手创建 Task,很快会出现取消失效、重复请求、UI 状态被旧响应覆盖的问题。

把生命周期交给调用方

ViewController 或 ViewModel 应该持有任务引用,并在页面消失、条件变化或新请求开始时取消旧任务。网络层只负责响应取消,不应该替 UI 猜生命周期。

final class ProfileViewModel {
    private var loadingTask: Task<Void, Never>?

    func load(userID: String) {
        loadingTask?.cancel()
        loadingTask = Task { [weak self] in
            do {
                let profile = try await self?.service.profile(id: userID)
                try Task.checkCancellation()
                await MainActor.run { self?.state = .loaded(profile) }
            } catch is CancellationError {
                return
            } catch {
                await MainActor.run { self?.state = .failed(error) }
            }
        }
    }
}

主线程边界必须显式

业务层可以在后台 await 网络和解析,但状态写入、UIKit 调用和可观察属性更新必须回到 MainActor。不要依赖“现在看起来没问题”。

错误要被翻译

底层错误不应该直接冲到 UI。把网络错误、业务错误和取消分开,UI 才能展示明确状态,而不是把所有失败都当成同一个 toast。