mcpField
A
axiom-swift-concurrency-ref

axiom-swift-concurrency-ref

github.com/charleswiltgen开发1.2k90 次安装今天更新
代码仓库

关于 axiom-swift-concurrency-ref

面向开发者的 Swift 并发 API 参考技能,提供语法查询和可复用示例,涵盖 Actor、Sendable、任务管理、取消处理及常见并发问题。

主要功能

  1. 01Actor 隔离与重入
  2. 02Sendable 遵循模式
  3. 03Task 创建与取消
  4. 04任务优先级和局部值
  5. 05超时与引用循环模式
  6. 06并发常见问题指引

适用场景

  1. 01查询 Actor 隔离规则
  2. 02解决 Sendable 编译错误
  3. 03管理任务取消
  4. 04为异步操作添加超时

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swift-concurrency-ref

安装后在 Claude Code 中输入/axiom-swift-concurrency-ref确认已加载。

详细介绍

Swift 并发 API 参考

完整的 Swift 并发 API 参考,提供可复制粘贴的模式和语法查询。

补充 axiom-swift-concurrency(介绍何时以及为何使用并发,包括渐进式学习路径、决策树、@concurrent 和隔离化遵循)。

相关技能:axiom-swift-concurrency(渐进式学习路径、决策树)、axiom-synchronization(Mutex、锁)、axiom-assume-isolated(assumeIsolated 模式)

第 1 部分:Actor 模式

Actor 定义
actor ImageCache {
    private var cache: [URL: UIImage] = [:]

    func image(for url: URL) -> UIImage? {
        cache[url]
    }

    func store(_ image: UIImage, for url: URL) {
        cache[url] = image
    }
}

// Usage — must await across isolation boundary
let cache = ImageCache()
let image = await cache.image(for: url)

Actor 上的所有属性和方法默认都是隔离的。Actor 隔离域之外的调用方必须使用 await 来访问它们。

Actor 隔离规则

每个 Actor 的存储属性和方法都隔离于该 Actor。跨越隔离边界进行访问需要使用 await,它会挂起调用方,直到 Actor 能够处理请求。

actor Counter {
    var count = 0              // Isolated — external access requires await
    let name: String           // let constants are implicitly nonisolated

    func increment() {         // Isolated — await required from outside
        count += 1
    }

    nonisolated func identity() -> String {
        name                   // OK: accessing nonisolated let
    }
}

let counter = Counter(name: "main")
await counter.increment()      // Must await across isolation boundary
let id = counter.identity()    // No await needed — nonisolated
nonisolated 关键字

退出隔离,以便同步访问不可变状态。

actor MyActor {
    let id: UUID               // let constants are implicitly nonisolated

    nonisolated var description: String {
        "Actor \(id)"          // Can only access nonisolated state
    }

    nonisolated func hash(into hasher: inout Hasher) {
        hasher.combine(id)     // Only nonisolated properties
    }
}

nonisolated 方法无法访问任何隔离的存储属性。可将其用于要求同步访问的协议遵循(例如 Hashable、CustomStringConvertible)。

Actor 可重入性

Actor 内的挂起点(await)允许其他调用方交错执行。任意两个 await 表达式之间的状态都可能发生变化。

actor BankAccount {
    var balance: Double = 0

    func transfer(amount: Double, to other: BankAccount) async {
        guard balance >= amount else { return }
        balance -= amount
        // REENTRANCY HAZARD: another caller could modify balance here
        // while we await the deposit on the other actor
        await other.deposit(amount)
    }

    func deposit(_ amount: Double) {
        balance += amount
    }
}

模式:在 Actor 内的每个 await 之后重新检查状态:

actor BankAccount {
    var balance: Double = 0

    func transfer(amount: Double, to other: BankAccount) async -> Bool {
        guard balance >= amount else { return false }
        balance -= amount

        await other.deposit(amount)

        // Re-check invariants after await if needed
        return true
    }
}
全局 Actor

全局 Actor 提供一个可从任何位置访问的共享隔离域。

@globalActor
actor MyGlobalActor {
    static let shared = MyGlobalActor()
}

@MyGlobalActor
func doWork() { /* isolated to MyGlobalActor */ }

@MyGlobalActor
class MyService {
    var state: Int = 0         // Isolated to MyGlobalActor
}
@MainActor

用于 UI 工作的内置全局 Actor。所有 UI 更新都必须在 @MainActor 上进行。

@MainActor
class ViewModel: ObservableObject {
    @Published var items: [Item] = []

    func loadItems() async {
        let data = await fetchFromNetwork()
        items = data           // Safe: already on MainActor
    }
}

// Annotate individual members
class MixedService {
    @MainActor var uiState: String = ""

    @MainActor
    func updateUI() {
        uiState = "Done"
    }

    func backgroundWork() async -> String {
        await heavyComputation()
    }
}

子类继承:如果一个类标记为 @MainActor,其所有子类都会继承该隔离属性。

Actor 初始化器

Actor 初始化器不具备隔离属性。你无法在 init 中调用隔离方法。

actor DataManager {
    var data: [String] = []

    init() {
        // Cannot call isolated methods here
        // self.loadDefaults()  // ERROR: actor-isolated method in non-isolated init
    }

    // Use a factory method instead
    static func create() async -> DataManager {
        let manager = DataManager()
        await manager.loadDefaults()
        return manager
    }

    func loadDefaults() {
        data = ["default"]
    }
}
Actor 常见问题表
常见问题症状修复方法
Actor 可重入性await 之间状态发生变化每次 await 后重新检查状态
nonisolated 访问隔离状态编译器错误移除 nonisolated,或将属性设为 nonisolated
在同步上下文中调用 Actor 方法“Expression is 'async'”用 Task {} 包装,或将调用方设为异步
全局 Actor 继承子类继承 @MainActor有意识地决定哪些方法需要隔离
Actor 初始化器不具备隔离属性无法在 init 中调用隔离方法使用工厂方法,或在初始化后填充数据
Actor 协议遵循“Non-isolated” 遵循错误对协议方法使用 nonisolated,或使用隔离化遵循(Swift 6.2+)
使用 Actor 作为 ViewModel@Published 无法工作,UI 更新需要 await面向 UI 的代码使用 @MainActor 类,Actor 仅用于非 UI 共享状态
在 Actor 内使用 GCD 切换队列会破坏隔离保证,并有线程爆炸风险移除 GCD——Actor 隔离已能串行化访问

第 2 部分:Sendable 模式

自动 Sendable 遵循

当值类型的所有存储属性都满足 Sendable 时,该类型也满足 Sendable。

// Structs: Sendable when all stored properties are Sendable
struct UserProfile: Sendable {
    let name: String
    let age: Int
}

// Enums: Sendable when all associated values are Sendable
enum LoadState: Sendable {
    case idle
    case loading
    case loaded(String)        // String is Sendable
    case failed(Error)         // ERROR: Error is not Sendable
}

// Fix: use a Sendable error type
enum LoadState: Sendable {
    case idle
    case loading
    case loaded(String)
    case failed(any Error & Sendable)
}
@Sendable 闭包

跨隔离边界传递的闭包必须标记为 @Sendable。@Sendable 闭包不能捕获可变的局部状态。

func runInBackground(_ work: @Sendable () -> Void) {
    Task.detached { work() }
}

// All captured values must be Sendable
var count = 0
runInBackground {
    // ERROR: capture of mutable local variable
    // count += 1
}

let snapshot = count
runInBackground {
    print(snapshot)            // OK: let binding of Sendable type
}
@unchecked Sendable

手动保证线程安全。仅在你自行提供同步机制时使用。

final class ThreadSafeCache: @unchecked Sendable {
    private let lock = NSLock()
    private var storage: [String: Any] = [:]

    func get(_ key: String) -> Any? {
        lock.lock()
        defer { lock.unlock() }
        return storage[key]
    }

    func set(_ key: String, value: Any) {
        lock.lock()
        defer { lock.unlock() }
        storage[key] = value
    }
}
@unchecked Sendable 的要求
  • 类必须是 final
  • 所有可变状态都必须受到同步原语(锁、队列、Mutex)的保护
  • 你需要对正确性负责——编译器不会进行检查
条件遵循
struct Box<T> {
    let value: T
}

// Box is Sendable only when T is Sendable
extension Box: Sendable where T: Sendable {}

// Standard library uses this extensively:
// Array<Element>: Sendable where Element: Sendable
// Dictionary<Key, Value>: Sendable where Key: Sendable, Value: Sendable
// Optional<Wrapped>: Sendable where Wrapped: Sendable
sending 参数修饰符(SE-0430)

将值的所有权转移到另一个隔离边界。调用方会放弃访问权。

func process(_ value: sending String) async {
    // Caller can no longer access value after this call
    await store(value)
}

// Useful for transferring non-Sendable types when caller won't use them again
func handOff(_ connection: sending NetworkConnection) async {
    await manager.accept(connection)
}
构建设置

控制 Xcode 中 Sendable 检查的严格程度:

设置值行为
SWIFT_STRICT_CONCURRENCYminimal仅检查显式的 Sendable 注解
SWIFT_STRICT_CONCURRENCYtargeted检查推断的 Sendable 和闭包
SWIFT_STRICT_CONCURRENCYcomplete完整的严格并发检查(Swift 6 默认值)
Sendable 常见问题表
常见问题症状修复方法
类无法成为 Sendable“Class cannot conform to Sendable”将类设为 final 且不可变,或使用带锁的 @unchecked Sendable
闭包捕获了非 Sendable 类型“Capture of non-Sendable type”在捕获前复制值,或让类型符合 Sendable
协议无法要求 Sendable泛型约束复杂使用 where T: Sendable
@unchecked Sendable 掩盖了错误运行时发生数据竞争仅在锁或队列能确保安全时使用
Array/Dictionary 有条件地符合 Sendable集合仅在 Element 符合 Sendable 时才符合确保元素类型符合 Sendable
Error 不符合 Sendable“Type does not conform to Sendable”使用 any Error & Sendable 或类型化错误

第 3 部分:任务管理

Task { }

创建一个非结构化任务,该任务会继承当前 Actor 上下文和优先级。

// Inherits actor context — if called from @MainActor, runs on MainActor
let task = Task {
    try await fetchData()
}

// Get the result
let result = try await task.value

// Get Result<Success, Failure>
let outcome = await task.result
Task.detached { }

创建一个不继承上下文的任务。不会继承 Actor 或优先级。

Task.detached(priority: .background) {
    // NOT on MainActor even if created from MainActor
    await processLargeFile()
}

适用场景:必须不在调用方 Actor 上运行的后台工作。多数情况下优先使用 Task {} —— 很少需要 Task.detached。

任务取消

取消采用协作式机制。设置取消标记只是发出请求;任务必须检查并作出响应。

let task = Task {
    for item in largeCollection {
        // Option 1: Check boolean
        if Task.isCancelled { break }

        // Option 2: Throw CancellationError
        try Task.checkCancellation()

        await process(item)
    }
}

// Request cancellation
task.cancel()
Task.sleep

将当前任务挂起一段时间。支持取消——如果在睡眠期间被取消,则抛出 CancellationError。

// Duration-based (preferred)
try await Task.sleep(for: .seconds(2))
try await Task.sleep(for: .milliseconds(500))

// Nanoseconds (older API)
try await Task.sleep(nanoseconds: 2_000_000_000)
Task.yield

主动让出执行权,以便其他任务运行。用于长时间运行的同步循环。

for i in 0..<1_000_000 {
    if i.isMultiple(of: 1000) {
        await Task.yield()
    }
    process(i)
}
任务优先级
优先级使用场景
.userInitiated用户直接发起的操作,结果可见
.high与 .userInitiated 相同
.medium未指定时的默认值
.low预取、非紧急工作
.utility长时间计算,显示进度
.background维护、清理等非时间敏感工作
Task(priority: .userInitiated) {
    await loadVisibleContent()
}

Task(priority: .background) {
    await cleanupTempFiles()
}
@TaskLocal

任务作用域内的值会自动传播到子任务。

enum RequestContext {
    @TaskLocal static var requestID: String?
    @TaskLocal static var userID: String?
}

// Set values for a scope
RequestContext.$requestID.withValue("req-123") {
    RequestContext.$userID.withValue("user-456") {
        // Both values available here and in child tasks
        Task {
            print(RequestContext.requestID)  // "req-123"
            print(RequestContext.userID)     // "user-456"
        }
    }
}

// Outside scope — values are nil
print(RequestContext.requestID)  // nil

传播规则:@TaskLocal 值会传播到通过 Task {} 创建的子任务,但不会传播到 Task.detached {}。

任务超时模式

使用任务组竞速,为任意异步操作设置截止时间:

func withTimeout<T: Sendable>(
    _ duration: Duration,
    operation: @Sendable @escaping () async throws -> T
) async throws -> T {
    try await withThrowingTaskGroup(of: T.self) { group in
        group.addTask { try await operation() }
        group.addTask {
            try await Task.sleep(for: duration)
            throw TimeoutError()
        }
        guard let result = try await group.next() else {
            throw TimeoutError()
        }
        group.cancelAll()  // Cancel the loser — without this it keeps running
        return result
    }
}

group.cancelAll() 至关重要。没有它,落败的任务(超时任务或操作任务)会继续运行,直到任务组作用域退出。

任务保留环

任务会像闭包一样捕获变量。引用 self 的已存储任务会形成保留环。

// ❌ Retain cycle: self → task → self
task = Task {
    while true { await self.poll() }
}

// ✅ Weak capture breaks the cycle
task = Task { [weak self] in
    while let self, !Task.isCancelled {
        await self.poll()
    }
}

规则:当 Task 作为属性存储,或遍历无限异步序列时,使用 [weak self]。能快速完成的短生命周期 Task 可以使用强引用捕获。

Swift 6 中的 Thread.current

在 Swift 6 语言模式下,异步上下文中无法使用 Thread.current:

// ❌ Compiler error in Swift 6 mode
func check() async { print(Thread.current) }

// ✅ Workaround for debugging only
extension Thread {
    static var currentThread: Thread { Thread.current }
}

不要依赖线程标识来确保正确性——任务会在挂起点之间切换线程。应考虑隔离域。

任务常见问题表
常见问题现象修复方法
任务从未取消资源泄漏,视图消失后工作仍在继续存储任务,并在 deinit/onDisappear 中取消
忽略取消即使已取消,任务仍会运行至完成在循环中检查 Task.isCancelled,并使用 checkCancellation()
Task.detached 丢失 Actor 上下文“未隔离到 MainActor”需要 Actor 隔离时使用 Task {}
在存储的 Task 中捕获 self循环引用,永远不会调用 deinit对长期运行或存储的任务使用 [weak self]
认为 async = 后台运行代码仍在调用方 Actor 上运行使用 @concurrent 强制在后台执行
TaskLocal 未传播detached 任务中的值为 nilTaskLocal 只会传播到子任务,不会传播到 detached 任务
任务优先级反转低优先级任务阻塞高优先级任务系统会处理大多数情况;避免让高优先级任务等待低优先级任务
在 async 上下文中使用 Thread.currentSwift 6 模式下编译器报错不要依赖线程身份,改用隔离域

第 4 部分:结构化并发

async let

并行运行固定数量的操作。离开作用域时,所有 async let 绑定都会被隐式等待。

async let images = fetchImages()
async let metadata = fetchMetadata()
async let config = loadConfig()

// All three run concurrently, await together
let (imgs, meta, cfg) = try await (images, metadata, config)

语义:如果某个 async let 抛出错误,其他任务会被取消。退出外围作用域前,所有任务都必须完成(或被取消)。

TaskGroup — 不抛出错误

动态运行数量不定的并行任务,且这些任务都不会抛出错误。

let results = await withTaskGroup(of: String.self) { group in
    for name in names {
        group.addTask {
            await fetchGreeting(for: name)
        }
    }

    var greetings: [String] = []
    for await greeting in group {
        greetings.append(greeting)
    }
    return greetings
}
TaskGroup — 抛出错误

动态运行数量不定的并行任务,这些任务可能会抛出错误。

let images = try await withThrowingTaskGroup(of: (URL, UIImage).self) { group in
    for url in urls {
        group.addTask {
            let image = try await downloadImage(url)
            return (url, image)
        }
    }

    var results: [URL: UIImage] = [:]
    for try await (url, image) in group {
        results[url] = image
    }
    return results
}
withDiscardingTaskGroup(iOS 17+)

适用于需要并发,但不需要收集结果的情况。相比常规 TaskGroup 更节省内存,因为不会存储结果。

try await withThrowingDiscardingTaskGroup { group in
    for connection in connections {
        group.addTask {
            try await connection.monitor()
            // Results are discarded — useful for long-running services
        }
    }
    // Group stays alive until all tasks complete or one throws
}
实际应用模式 — 合并多个通知流
extension NotificationCenter {
    func notifications(named names: [Notification.Name]) -> AsyncStream<Void> {
        AsyncStream { continuation in
            let task = Task {
                await withDiscardingTaskGroup { group in
                    for name in names {
                        group.addTask {
                            for await _ in self.notifications(named: name) {
                                continuation.yield()
                            }
                        }
                    }
                }
                continuation.finish()
            }
            continuation.onTermination = { _ in task.cancel() }
        }
    }
}
TaskGroup 控制
await withTaskGroup(of: Data.self) { group in
    // Add tasks conditionally
    group.addTaskUnlessCancelled {
        await fetchData()
    }

    // Cancel remaining tasks
    group.cancelAll()

    // Wait without collecting
    await group.waitForAll()

    // Iterate one at a time
    while let result = await group.next() {
        process(result)
    }
}
任务树语义

结构化并发会形成一棵树:

  • 父任务取消会取消所有子任务 — 取消一个任务会取消所有 async let 和 TaskGroup 子任务
  • 子任务错误会传播到父任务 — 在可抛出错误的组中,子任务错误会取消同级任务并向上传播
  • 父任务返回前,所有子任务都必须完成 — 作用域会等待所有子任务,即使它们已被取消
// If fetchImages() throws, fetchMetadata() is automatically cancelled
async let images = fetchImages()
async let metadata = fetchMetadata()
let result = try await (images, metadata)
结构化并发常见问题表
常见问题现象修复方法
未使用 async let工作仍会执行,但结果会被静默丢弃为所有 async let 结果赋值,或使用 withDiscardingTaskGroup
TaskGroup 累积内存任务达到 1 万个以上时内存持续增长结果到达时立即处理,不要全部收集
在 addTask 中捕获可变状态“修改捕获的 var”使用 let 绑定或 Actor
未处理部分失败部分任务成功,部分任务失败使用 group.next() 并单独处理错误
在循环中使用 async let编译器报错 — async let 必须位于固定位置改用 TaskGroup
提前从组中返回剩余任务仍会运行返回前调用 group.cancelAll()

第 5 部分:异步序列

AsyncStream

用于随时间生成值的非抛出错误流。

let stream = AsyncStream<Int> { continuation in
    for i in 0..<10 {
        continuation.yield(i)
    }
    continuation.finish()
}

for await value in stream {
    print(value)
}
AsyncThrowingStream

可能因错误而失败的流。

let stream = AsyncThrowingStream<Data, Error> { continuation in
    let monitor = NetworkMonitor()
    monitor.onData = { data in
        continuation.yield(data)
    }
    monitor.onError = { error in
        continuation.finish(throwing: error)
    }
    monitor.onComplete = {
        continuation.finish()
    }

    continuation.onTermination = { @Sendable _ in
        monitor.stop()
    }

    monitor.start()
}

do {
    for try await data in stream {
        process(data)
    }
} catch {
    handleStreamError(error)
}
Continuation API
let stream = AsyncStream<Value> { continuation in
    // Emit a value
    continuation.yield(value)

    // End the stream normally
    continuation.finish()

    // Cleanup when consumer cancels or stream ends
    continuation.onTermination = { @Sendable termination in
        switch termination {
        case .cancelled:
            cleanup()
        case .finished:
            finalCleanup()
        @unknown default:
            break
        }
    }
}

// For throwing streams
let stream = AsyncThrowingStream<Value, Error> { continuation in
    continuation.yield(value)
    continuation.finish()                  // Normal end
    continuation.finish(throwing: error)   // End with error
}
缓冲策略

控制值的生成速度快于消费速度时会发生什么。

// Keep all values (default) — memory can grow unbounded
let stream = AsyncStream<Int>(bufferingPolicy: .unbounded) { continuation in
    // ...
}

// Keep oldest N values, drop new ones when buffer is full
let stream = AsyncStream<Int>(bufferingPolicy: .bufferingOldest(100)) { continuation in
    // ...
}

// Keep newest N values, drop old ones when buffer is full
let stream = AsyncStream<Int>(bufferingPolicy: .bufferingNewest(100)) { continuation in
    // ...
}
策略行为适用场景
.unbounded保留所有值消费者能跟上,或生产者有界
.bufferingOldest(N)缓冲区满时丢弃新值顺序很重要,较旧的值优先
.bufferingNewest(N)缓冲区满时丢弃旧值最新状态很重要(UI 更新、传感器数据)
自定义 AsyncSequence
struct Counter: AsyncSequence {
    typealias Element = Int
    let limit: Int

    struct AsyncIterator: AsyncIteratorProtocol {
        var current = 0
        let limit: Int

        mutating func next() async -> Int? {
            guard current < limit else { return nil }
            defer { current += 1 }
            return current
        }
    }

    func makeAsyncIterator() -> AsyncIterator {
        AsyncIterator(limit: limit)
    }
}

// Usage
for await number in Counter(limit: 5) {
    print(number)  // 0, 1, 2, 3, 4
}
AsyncSequence 运算符

标准运算符适用于任何 AsyncSequence:

// Map
for await name in users.map(\.name) { }

// Filter
for await adult in users.filter({ $0.age >= 18 }) { }

// CompactMap
for await image in urls.compactMap({ await tryLoadImage($0) }) { }

// Prefix
for await first5 in stream.prefix(5) { }

// first(where:)
let match = await stream.first(where: { $0 > threshold })

// Contains
let hasMatch = await stream.contains(where: { $0 > threshold })

// Reduce
let sum = await numbers.reduce(0, +)
内置异步序列
// NotificationCenter
for await notification in NotificationCenter.default.notifications(named: .didUpdate) {
    handleUpdate(notification)
}

// URLSession bytes
let (bytes, response) = try await URLSession.shared.bytes(from: url)
for try await byte in bytes {
    process(byte)
}

// FileHandle bytes
for try await line in FileHandle.standardInput.bytes.lines {
    process(line)
}
异步序列注意事项表
注意事项表现修复方法
完成后仍生成后续值运行时警告,值丢失跟踪完成状态,在生成值前进行检查
流始终未完成for-await 循环永久挂起在所有代码路径中都调用 continuation.finish()
没有 onTermination 处理程序消费者取消时发生资源泄漏设置 continuation.onTermination 以进行清理
缓冲区无界高负载下内存不断增长使用 .bufferingNewest(N) 或 .bufferingOldest(N)
多个消费者只有第一个消费者能收到值AsyncStream 是单消费者的;为每个消费者创建单独的流
在 MainActor 上使用 for-await等待值时 UI 冻结使用 Task {} 在主路径之外进行消费

第 6 部分:隔离模式

函数上的 @MainActor
@MainActor
func updateUI() {
    label.text = "Done"
}

// Call from async context
func doWork() async {
    let result = await computeResult()
    await updateUI()           // Hops to MainActor
}
MainActor.run

从任何上下文中显式在主 Actor 上执行闭包。

func processData() async {
    let result = await heavyComputation()

    await MainActor.run {
        self.label.text = result
        self.progressView.isHidden = true
    }
}
MainActor.assumeIsolated(iOS 17+)

断言代码当前已在主 Actor 上运行。如果断言为假,则在运行时崩溃。

func legacyCallback() {
    // We KNOW this is called on main thread (UIKit guarantee)
    MainActor.assumeIsolated {
        self.viewModel.update()    // Access @MainActor state
    }
}

如需了解全面的模式,请参阅 axiom-assume-isolated。

nonisolated

退出外层 Actor 的隔离。

@MainActor
class ViewModel {
    let id: UUID                           // Implicitly nonisolated (let)

    nonisolated var analyticsID: String {   // Explicitly nonisolated
        id.uuidString
    }

    var items: [Item] = []                 // Isolated to MainActor
}
nonisolated(unsafe)

编译器的逃生口。告知编译器将属性视为未隔离,但不提供任何安全保证。

// Use only when you have external guarantees of thread safety
nonisolated(unsafe) var legacyState: Int = 0

// Common for global constants that the compiler can't verify
nonisolated(unsafe) let formatter: DateFormatter = {
    let f = DateFormatter()
    f.dateStyle = .medium
    return f
}()

警告:nonisolated(unsafe) 不提供任何运行时保护。数据竞争不会被捕获。仅在桥接旧代码时将其作为最后手段使用。

@preconcurrency

迁移期间,抑制针对并发前 API 的并发警告。

// Suppress warnings for entire module
@preconcurrency import MyLegacyFramework

// Suppress for specific protocol conformance
class MyDelegate: @preconcurrency SomeLegacyDelegate {
    func delegateCallback() {
        // No Sendable warnings for this conformance
    }
}
#isolation(Swift 6.0+)

捕获调用方的隔离上下文,使函数在调用方所在的任意 Actor 上运行。

func doWork(isolation: isolated (any Actor)? = #isolation) async {
    // Runs on caller's actor — no hop if caller is already isolated
    performWork()
}

// Called from @MainActor — runs on MainActor
@MainActor
func setup() async {
    await doWork()             // doWork runs on MainActor
}

// Called from custom actor — runs on that actor
actor MyActor {
    func run() async {
        await doWork()         // doWork runs on MyActor
    }
}
Task 闭包中的 #isolation 捕获(SE-0420)

创建需要处理非 Sendable 类型的 Task 闭包时,捕获隔离参数以继承调用方的上下文。

func process(
    delegate: NonSendableDelegate,
    isolation: isolated (any Actor)? = #isolation
) {
    Task {
        _ = isolation          // Forces capture — Task inherits caller's isolation
        delegate.doWork()      // ✅ Safe: running on caller's actor
    }
}

为什么需要 _ = isolation:根据 SE-0420,只有当闭包捕获了隔离参数的非可选绑定时,Task 闭包才会继承隔离上下文。_ = isolation 语句会强制进行此捕获。没有它,Task 会在默认执行器上运行,捕获非 Sendable 值会导致编译器错误。

适用场景:创建需要处理非 Sendable 委托对象的 Task、需要访问调用方状态的即发即忘异步工作,或在保持委托对象存活的同时桥接基于回调的 API。

隔离注意事项表
注意事项表现修复方法
从 MainActor 调用 MainActor.run不必要的切换,可能存在死锁风险检查上下文或使用 assumeIsolated
nonisolated(unsafe) 数据竞争运行时崩溃,状态损坏使用适当的隔离或 Mutex
@preconcurrency 隐藏真实问题生产环境中发生运行时崩溃发布前迁移到适当的并发方案
5.9 之前不可用 #isolation编译器错误使用传统的 @MainActor 注解
在 Task 中未捕获 #isolation捕获非 Sendable 值时出错在 Task 闭包中添加 _ = isolation(SE-0420)
在 Actor 方法上使用 nonisolated无法访问任何隔离状态仅用于基于非隔离状态的计算属性
在异步上下文中使用 Thread.currentSwift 6 模式下出现编译器错误不要依赖线程标识;应基于隔离域进行推理

第 7 部分:续体

将基于回调的 API 桥接到 async/await。

withCheckedContinuation

非抛出桥接。

func currentLocation() async -> CLLocation {
    await withCheckedContinuation { continuation in
        locationManager.requestLocation { location in
            continuation.resume(returning: location)
        }
    }
}
withCheckedThrowingContinuation

可抛出桥接。

func fetchUser(id: String) async throws -> User {
    try await withCheckedThrowingContinuation { continuation in
        api.fetchUser(id: id) { result in
            switch result {
            case .success(let user):
                continuation.resume(returning: user)
            case .failure(let error):
                continuation.resume(throwing: error)
            }
        }
    }
}
续体恢复方法
// Return a value
continuation.resume(returning: value)

// Throw an error
continuation.resume(throwing: error)

// From a Result type
continuation.resume(with: result)    // Result<T, Error>
恰好恢复一次规则

续体必须恰好恢复一次:

  • 恢复两次会导致崩溃,错误为 "Continuation already resumed"(checked),或产生未定义行为(unsafe)
  • 从不恢复会导致等待中的任务永久挂起,造成无声泄漏
// DANGEROUS: callback might not be called
func riskyBridge() async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
        api.fetch { data, error in
            if let error {
                continuation.resume(throwing: error)
                return
            }
            if let data {
                continuation.resume(returning: data)
                return
            }
            // BUG: if both are nil, continuation is never resumed
            // Fix: add a fallback
            continuation.resume(throwing: BridgeError.noResponse)
        }
    }
}
桥接委托
class LocationBridge: NSObject, CLLocationManagerDelegate {
    private var continuation: CheckedContinuation<CLLocation, Error>?
    private let manager = CLLocationManager()

    func requestLocation() async throws -> CLLocation {
        try await withCheckedThrowingContinuation { continuation in
            self.continuation = continuation
            manager.delegate = self
            manager.requestLocation()
        }
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        continuation?.resume(returning: locations[0])
        continuation = nil     // Prevent double resume
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        continuation?.resume(throwing: error)
        continuation = nil
    }
}
不安全续体

跳过运行时检查以提升性能。API 与经过检查的版本相同,但误用会导致未定义行为,而不是诊断性崩溃。

func fastBridge() async -> Data {
    await withUnsafeContinuation { continuation in
        // No runtime check for double-resume or missing resume
        fastCallback { data in
            continuation.resume(returning: data)
        }
    }
}

开发期间请使用经过检查的续体,只有在彻底测试后,且性能分析显示检查已成为瓶颈时,才切换到不安全版本。

续体常见问题表
常见问题症状修复方法
Resume 调用两次发生“Continuation already resumed”崩溃调用 resume 后将 continuation 设为 nil
从未调用 Resume任务无限期挂起确保所有代码路径都会调用 resume,包括错误和 nil 情况
捕获 continuationcontinuation 超出作用域将其存储在属性中,并确保只调用一次 resume
在调试模式下使用不安全续体误用时不会提供诊断信息开发期间使用 withCheckedContinuation
委托被多次调用第二次调用 resume 时崩溃对于重复回调,请使用 AsyncStream,而不是续体
回调在错误线程上执行对续体没有影响可以从任何线程调用续体的 resume

第 8 部分:迁移模式

从 GCD 和完成处理程序迁移到 Swift 并发的常见方式。

从 DispatchQueue 迁移到 Actor
// BEFORE: DispatchQueue for thread safety
class ImageCache {
    private let queue = DispatchQueue(label: "cache", attributes: .concurrent)
    private var cache: [URL: UIImage] = [:]

    func get(_ url: URL, completion: @escaping (UIImage?) -> Void) {
        queue.async { completion(self.cache[url]) }
    }

    func set(_ url: URL, image: UIImage) {
        queue.async(flags: .barrier) { self.cache[url] = image }
    }
}

// AFTER: Actor
actor ImageCache {
    private var cache: [URL: UIImage] = [:]

    func get(_ url: URL) -> UIImage? {
        cache[url]
    }

    func set(_ url: URL, image: UIImage) {
        cache[url] = image
    }
}
从 DispatchGroup 迁移到 TaskGroup
// BEFORE: DispatchGroup
let group = DispatchGroup()
var results: [Data] = []
for url in urls {
    group.enter()
    fetch(url) { data in
        results.append(data)
        group.leave()
    }
}
group.notify(queue: .main) { use(results) }

// AFTER: TaskGroup
let results = await withTaskGroup(of: Data.self) { group in
    for url in urls {
        group.addTask { await fetch(url) }
    }
    var collected: [Data] = []
    for await data in group {
        collected.append(data)
    }
    return collected
}
use(results)
从完成处理程序迁移到 async
// BEFORE
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
    URLSession.shared.dataTask(with: url) { data, _, error in
        if let error { completion(.failure(error)); return }
        guard let data else { completion(.failure(FetchError.noData)); return }
        completion(.success(data))
    }.resume()
}

// AFTER
func fetchData() async throws -> Data {
    let (data, _) = try await URLSession.shared.data(from: url)
    return data
}
使用 @MainActor 的 @objc 委托
@MainActor
class ViewController: UIViewController, UITableViewDelegate {
    // @objc delegate methods inherit @MainActor isolation from the class
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // Already on MainActor — safe to update UI
        updateSelection(indexPath)
    }
}
从 NotificationCenter 迁移到 AsyncSequence
// BEFORE
let observer = NotificationCenter.default.addObserver(
    forName: .didUpdate, object: nil, queue: .main
) { notification in
    handleUpdate(notification)
}
// Must remove observer in deinit

// AFTER
let task = Task {
    for await notification in NotificationCenter.default.notifications(named: .didUpdate) {
        await handleUpdate(notification)
    }
}
// Cancel task in deinit — no manual observer removal needed
从 Timer 迁移到 AsyncSequence
// BEFORE
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
    updateUI()
}
// Must invalidate in deinit

// AFTER
let task = Task {
    while !Task.isCancelled {
        await updateUI()
        try? await Task.sleep(for: .seconds(1))
    }
}
// Cancel task in deinit
从 DispatchSemaphore 迁移到 Actor
// BEFORE: Semaphore to limit concurrent operations
let semaphore = DispatchSemaphore(value: 3)
for url in urls {
    DispatchQueue.global().async {
        semaphore.wait()
        defer { semaphore.signal() }
        download(url)
    }
}

// AFTER: TaskGroup with limited concurrency
await withTaskGroup(of: Void.self) { group in
    var inFlight = 0
    for url in urls {
        if inFlight >= 3 {
            await group.next()   // Wait for one to finish
            inFlight -= 1
        }
        group.addTask { await download(url) }
        inFlight += 1
    }
    await group.waitForAll()
}
迁移常见问题表
常见问题症状修复方法
从 DispatchQueue.sync 迁移到 actor可能导致死锁移除 .sync,改用 await
全局派发导致 actor 竞争串行化导致性能下降使用 Concurrency Instruments 进行性能分析
遗留委托 + Sendable“Cannot conform to Sendable”使用 @preconcurrency import 或 @MainActor 隔离
回调被多次调用续体崩溃使用 AsyncStream,而不是续体
在异步上下文中调用 Semaphore.wait线程饥饿,可能导致死锁使用 TaskGroup 并手动限制并发
从 DispatchQueue.main.async 迁移到 MainActor时序上存在细微差异MainActor.run 是等效方式——请测试边缘情况
用顶层 Tasks 替换结构化任务丢失取消传播和错误处理对相关的并行工作使用 async let 或 TaskGroup
批量添加 @unchecked Sendable 以修复警告掩盖整个代码库中的真实数据竞争一次修复一个类型,正确使用 Sendable、actor 或 sending

API 速查

任务APISwift 版本
定义隔离类型actor MyActor { }5.5+
在主线程上运行@MainActor5.5+
标记为可安全共享: Sendable5.5+
标记闭包为可安全共享@Sendable5.5+
并行任务(固定)async let5.5+
并行任务(动态)withTaskGroup5.5+
流式传递值AsyncStream5.5+
桥接回调withCheckedContinuation5.5+
检查取消状态Task.checkCancellation()5.5+
任务作用域值@TaskLocal5.5+
断言隔离MainActor.assumeIsolated5.9+ (iOS 17+)
捕获调用方隔离#isolation6.0+
基于锁的同步Mutex6.0+ (iOS 18+)
丢弃结果withDiscardingTaskGroup5.9+ (iOS 17+)
转移所有权sending 参数6.0+
强制在后台运行@concurrent6.2+
隔离的一致性extension: @MainActor Proto6.2+

资源

WWDC:2021-10132、2021-10134、2022-110350、2025-268

文档:/swift/concurrency、/swift/actor、/swift/sendable、/swift/taskgroup

技能:swift-concurrency、assume-isolated、synchronization、concurrency-profiling

评论

支持 Markdown