swift-concurrency

swift-concurrency

热门

诊断 Swift 并发问题,将基于回调的代码重构为 async/await,并在处理任务、Actor、@MainActor、Sendable、数据竞争、线程安全或与并发相关的编译器和 linter 警告时指导 Swift 6 迁移。

1573Star
99Fork
更新于 2026/5/4
SKILL.md
readonly只读
name
swift-concurrency
description

诊断 Swift 并发问题,将基于回调的代码重构为 async/await,并在处理任务、Actor、@MainActor、Sendable、数据竞争、线程安全或与并发相关的编译器和 linter 警告时指导 Swift 6 迁移。

Swift 并发

快速路径

在提出修复方案之前:

  1. 分析 Package.swift.pbxproj 以确定 Swift 语言模式、严格并发级别、默认隔离和即将推出的功能。始终执行此操作,而不仅仅是迁移工作。
  2. 捕获确切的诊断信息和有问题的符号。
  3. 确定隔离边界:@MainActor、自定义 Actor、Actor 实例隔离或 nonisolated
  4. 确认代码是否与 UI 绑定或旨在脱离主 Actor 运行。当生成非结构化任务时,检查同步前缀(第一个 await 之前的所有内容):仅当该前缀确实需要主 Actor 访问时才在 @MainActor 上启动;否则使用 Task { @concurrent in ... } 并在挂起后通过 MainActor.run 跳回。一个简单的非主行(例如 print)后跟同一前缀中的主 Actor 工作并不是使用 @concurrent 的理由。对于延迟重试、定时器和退避任务,将等待与 UI 修改分开。即使最终状态更新属于主 Actor,sleep 通常也应在主 Actor 之外进行。

更改并发行为的项目设置:

设置 SwiftPM (Package.swift) Xcode (.pbxproj)
语言模式 swiftLanguageVersions-swift-version// swift-tools-version: 不是可靠代理) Swift 语言版本
严格并发 .enableExperimentalFeature("StrictConcurrency=targeted") SWIFT_STRICT_CONCURRENCY
默认隔离 .defaultIsolation(MainActor.self) SWIFT_DEFAULT_ACTOR_ISOLATION
即将推出的功能 .enableUpcomingFeature("NonisolatedNonsendingByDefault") SWIFT_UPCOMING_FEATURE_*
可接近的并发 不适用(使用单独的即将推出的功能) SWIFT_APPROACHABLE_CONCURRENCY

Xcode 26 注意:在 Xcode 26 中创建的新项目通常默认启用 SWIFT_DEFAULT_ACTOR_ISOLATION = MainActorSWIFT_APPROACHABLE_CONCURRENCY = YES。将这些视为新创建项目的可能默认值,而不是已确认的设置。

如果其中任何一项未知,请要求开发者确认,然后再给出迁移敏感的指导。不要猜测,即使对于新的 Xcode 26 项目也是如此。

防护措施:

  • 不要推荐将 @MainActor 作为万能修复。证明代码确实与 UI 绑定。
  • 优先使用结构化并发而非非结构化任务。仅在明确理由下使用 Task.detached
  • 如果推荐 @preconcurrency@unchecked Sendablenonisolated(unsafe),则需要记录安全不变性并制定后续移除计划。
  • 优化最小安全更改。在迁移期间不要重构不相关的架构。
  • 课程参考资料仅用于深入学习。谨慎使用,仅当它们明确有助于回答开发者的问题时。

快速修复模式

当以下所有条件成立时,使用快速修复模式:

  • 问题局限于一个文件或一个类型。
  • 隔离边界清晰。
  • 修复可以用 1-2 个保持行为的步骤解释。

当以下任何条件成立时,跳过快速修复模式:

  • 构建设置或默认隔离未知。
  • 问题跨越模块边界或更改公共 API 行为。
  • 可能的修复依赖于不安全的逃生口。

常见诊断

诊断 首先检查 最小安全修复 升级到
Main actor-isolated ... cannot be used from a nonisolated context 这确实与 UI 绑定吗? 将调用者隔离到 @MainActor,或仅在主 Actor 所有权正确时使用 await MainActor.run { ... } references/actors.mdreferences/threading.md
Actor-isolated type does not conform to protocol 要求必须在 Actor 上运行吗? 优先使用隔离的一致性(例如 extension Foo: @MainActor SomeProtocol);仅对真正非隔离的要求使用 nonisolated references/actors.md
Sending value of non-Sendable type ... risks causing data races 正在跨越哪个隔离边界? 将访问保持在一个 Actor 内,或将传输的值转换为不可变/值类型。 references/sendable.mdreferences/threading.md
SwiftLint async_without_await async 是否确实由协议、重写或 @concurrent 要求? 移除 async,或使用带有理由的窄抑制。永远不要添加虚假的 await。 references/linting.md
wait(...) is unavailable from asynchronous contexts 这是遗留的 XCTest 异步等待吗? 替换为 await fulfillment(of:) 或 Swift Testing 等效项。 references/testing.md
Core Data 并发警告 NSManagedObject 实例是否跨越上下文或 Actor? 传递 NSManagedObjectID 或映射到 Sendable 值类型。 references/core-data.md
Thread.current unavailable from asynchronous contexts` 您是否通过线程而不是隔离进行调试? 根据隔离进行推理,并使用 Instruments/调试器代替。 references/threading.md
SwiftLint 并发相关警告 触发了哪个特定的 lint 规则? 使用 references/linting.md 了解规则意图和首选修复;避免虚假的 await。 references/linting.md
... cannot satisfy conformance requirement for a 'Sendable' type parameter (SendableMetatype) 一致性是否带有全局 Actor 隔离? 从一致性中移除 Actor 隔离,或避免跨隔离边界传递元类型。请参阅 references/actors.md 中的 SendableMetatype 部分。 references/actors.md

当快速修复失败时

  1. 如果尚未确认,请收集项目设置。
  2. 重新评估类型跨越的隔离边界。
  3. 路由到匹配的参考文件以进行更深入的修复。
  4. 如果修复可能改变行为,请记录不变性并添加验证步骤。

最小安全修复

优先选择在满足数据竞争安全的同时保持行为的更改:

  • UI 绑定状态:将类型或成员隔离到 @MainActor
  • 共享可变状态:将其移动到 actor 后面,或仅当状态由 UI 拥有时使用 @MainActor
  • 后台工作:当工作必须跳出调用者隔离时,使用标记为 @concurrentasync API;当工作可以安全继承调用者隔离时,使用不带 @concurrentnonisolated。当生成 Task 时,将入口隔离与其同步前缀匹配。如果第一个 await 之前没有任何内容需要主 Actor,则使用 Task { @concurrent in ... } 并通过 await MainActor.run { ... } 跳回以进行 UI 更新。如果前缀混合了简单的非主语句和主 Actor 工作,则保持继承的 @MainActor 启动——将廉价的行拆分到主 Actor 之外不值得额外的跳转。
  • Sendability 问题:优先使用不可变值和显式边界,而不是 @unchecked Sendable

并发工具选择

需求 工具 关键指导
单个异步操作 async/await 顺序异步工作的默认选择
固定并行操作 async let 编译时已知数量;抛出时自动取消
动态并行操作 withTaskGroup 未知数量;结构化——在作用域退出时取消子任务
同步到异步桥接 Task { } 继承 Actor 上下文;仅在记录理由时使用 Task.detached
共享可变状态 actor 优先于锁/队列;保持隔离部分小巧
UI 绑定状态 @MainActor 仅用于真正与 UI 相关的代码;证明隔离的合理性

常见场景

带 UI 更新的网络请求

Task { @concurrent in
    let data = try await fetchData()
    await MainActor.run { self.updateUI(with: data) }
}

并行处理数组项

await withTaskGroup(of: ProcessedItem.self) { group in
    for item in items {
        group.addTask { await process(item) }
    }
    for await result in group {
        results.append(result)
    }
}

任务入口隔离

Task 的入口隔离与其同步前缀(从 { 到第一个 await 的所有内容)匹配。

  • 如果该前缀中的任何内容需要 @MainActor,则保持继承的 @MainActor 启动。
  • 如果该前缀中没有任何内容需要 @MainActor,则优先使用 Task { @concurrent in ... } 并仅对 UI 拥有的修改跳回。
// ❌ 同步前缀为空;第一个工作跳转到其他隔离域
Task {
    await hopToOtherIsolationDomain()
}

// ❌ 同步前缀仅为 `print`(简单,非主);第一个 await 跳转到其他隔离域
Task {
    print("Also not main-thread-bound")
    await hopToOtherIsolationDomain()
}

// ✅ 在主 Actor 之外启动,仅对 UI 工作跳回
Task { @concurrent in
    await hopToOtherIsolationDomain()
    await MainActor.run { updateUI() }
}

// ✅ 同步前缀确实包含主 Actor 工作——保持继承
Task {
    print("debug")              // 简单,非主——随行
    self.isLoading = true       // 需要 @MainActor,在任何 await 之前
    await fetchData()
}

Swift 6 迁移快速指南

Swift 6 的关键更改:

  • 严格并发检查默认启用
  • 编译时完全数据竞争安全
  • Sendable 要求在边界强制执行
  • 所有异步边界的隔离检查

迁移验证循环

对每个迁移更改应用此循环:

  1. 构建 — 运行 swift build 或 Xcode 构建以显示新的诊断信息
  2. 修复 — 一次解决一个错误类别(例如,首先解决所有 Sendable 问题)
  3. 重新构建 — 确认修复编译干净后再继续
  4. 测试 — 运行测试套件以捕获回归(swift test 或 Cmd+U)
  5. 仅当所有诊断都解决后才继续下一个文件/模块

如果修复引入了新警告,请在继续之前解决它们。切勿批量处理多个不相关的修复——保持提交小巧且可审查。

有关详细的迁移步骤,请参阅 references/migration.md

参考路由器

打开与问题匹配的最小参考:

  • 基础
    • references/async-await-basics.md — async/await 语法、执行顺序、async let、URLSession 模式
    • references/tasks.md — Task 生命周期、取消、优先级、任务组、结构化与非结构化
    • references/actors.md — Actor 隔离、@MainActor、全局 Actor、可重入性、自定义执行器、Mutex
    • references/sendable.md — Sendable 一致性、值/引用类型、@unchecked、区域隔离
    • references/threading.md — 执行模型、挂起点、Swift 6.2 隔离行为
    • references/async-sequences.md — AsyncSequence、AsyncStream、何时使用 vs 常规 async 方法
    • references/async-algorithms.md — 防抖、节流、合并、combineLatest、通道、定时器
  • 应用主题
    • references/testing.md — 优先使用 Swift Testing,XCTest 回退,泄漏检查
    • references/performance.md — 使用 Instruments 分析,减少挂起点,执行策略
    • references/memory-management.md — 任务中的循环引用,内存安全模式
    • references/core-data.md — NSManagedObject sendability,自定义执行器,隔离冲突
  • 迁移和工具
    • references/migration.md — Swift 6 迁移策略,闭包到 async 转换,@preconcurrency,FRP 迁移
    • references/linting.md — 并发相关的 lint 规则和 SwiftLint async_without_await
  • 词汇表
    • references/glossary.md — 核心并发术语的快速定义

验证检查清单

更改并发代码时:

  1. 在解释诊断之前重新检查构建设置。
  2. 构建并清除一个错误类别后再继续。不要将不相关的修复批处理到同一更改中。
  3. 运行测试,特别是 Actor、生命周期和取消敏感测试。
  4. 使用 Instruments 进行性能声明,而不是猜测。
  5. 验证长时间运行任务的释放和取消行为。
  6. 在长时间运行的操作中检查 Task.isCancelled
  7. 在异步上下文中,当 Actor 隔离或 Mutex 可以更安全地表达所有权时,切勿使用信号量或临时锁定。

注意:此技能基于 Antoine van der Lee 的全面 Swift 并发课程