关于 swift-concurrency
swift-concurrency 是为 Swift 开发者提供并发诊断与迁移指导的技能,涵盖隔离、Sendable、任务和测试。它强调先核对项目设置与具体诊断,再采用尽可能小且安全的修复。
主要功能
- 01分析 Swift 并发构建设置
- 02定位隔离边界与并发诊断
- 03提供常见错误的修复指引
- 04按主题路由到参考文档
- 05指导 Swift 6 迁移与验证
适用场景
- 01排查 MainActor 隔离错误
- 02解决 Sendable 数据传递问题
- 03规划 Swift 6 并发迁移
- 04验证并发修改的构建与测试
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ avdlee/ swift-concurrency-agent-skill --skill swift-concurrency安装后在 Claude Code 中输入/swift-concurrency确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlreferences/_index.mdactors.mdasync-algorithms.mdasync-await-basics.mdasync-sequences.mdcore-data.mdglossary.mdlinting.mdmemory-management.mdmigration.mdobservation.mdperformance.mdsendable.mdtasks.mdtesting.mdthreading.md详细介绍
Swift 并发
快速路径
提出修复方案前:
- 分析
Package.swift或.pbxproj,确定 Swift 语言模式、严格并发级别、默认隔离和即将启用的功能。始终执行此操作,不要只在迁移工作中执行。 - 获取准确的诊断信息和出问题的符号。
- 确定隔离边界:
@MainActor、自定义 Actor、Actor 实例隔离或nonisolated。 - 确认代码是否与 UI 绑定,还是旨在主 Actor 之外运行。创建非结构化任务时,检查同步前缀(第一个
await之前的所有内容):只有当前缀确实需要访问主 Actor 时,才从@MainActor启动;否则使用Task { @concurrent in ... },并且只在挂起后通过MainActor.run切回主 Actor。同步前缀中先执行一条无关紧要的非主 Actor 语句(例如print),再执行主 Actor 工作,并不能成为使用@concurrent的理由。对于延迟重试、计时器和退避任务,应将等待与 UI 修改分开。即使最终的状态更新属于主 Actor,休眠操作通常也应在主 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 = MainActor和SWIFT_APPROACHABLE_CONCURRENCY = YES。应将其视为新建项目中可能采用的默认设置,而非已确认的设置。
如果其中任何一项未知,请先让开发者确认,再提供会受迁移设置影响的指导。即使是新建的 Xcode 26 项目,也不要猜测。
注意事项:
- 不要把
@MainActor当作通用修复方案。应说明为何代码确实与 UI 绑定。 - 优先采用结构化并发,而非非结构化任务。只有理由明确时才使用
Task.detached。 - 如果建议使用
@preconcurrency、@unchecked Sendable或nonisolated(unsafe),必须要求提供有文档记录的安全不变量,并制定后续移除计划。 - 优先选择最小的安全改动。迁移期间不要重构无关架构。
- 课程参考仅用于深入学习。请谨慎使用,仅在能明确帮助解答开发者问题时才提供。
快速修复模式
符合以下所有条件时,使用快速修复模式:
- 问题仅涉及一个文件或一种类型。
- 隔离边界明确。
- 修复方案能用 1–2 个不改变行为的步骤说明。
符合以下任何条件时,跳过快速修复模式:
- 构建设置或默认隔离未知。
- 问题跨越模块边界或会改变公开 API 的行为。
- 可能的修复方案依赖不安全的逃逸机制。
常见诊断
| 诊断 | 首先检查 | 最小安全修复 | 升级至 |
|---|---|---|---|
Main actor-isolated ... cannot be used from a nonisolated context | 这确实与 UI 相关吗? | 将调用方隔离到 @MainActor,或仅在由主执行器负责管理确实合适时使用 await MainActor.run { ... }。 | references/actors.md、references/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.md、references/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 |
@Observable 隔离或 Sendable 错误 | @Observable 类是否标注了正确的 Actor? | 为 UI 状态添加 @MainActor;跨边界传递 Sendable 快照。 | references/observation.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 |
快速修复无效时
- 如果尚未确认,请收集项目设置。
- 重新评估该类型跨越了哪些隔离边界。
- 转至对应的参考文件,深入修复。
- 如果修复可能改变行为,请记录不变条件并添加验证步骤。
最小安全修复
优先选择在满足数据竞争安全的同时保留原有行为的更改:
- 与 UI 相关的状态:将类型或成员隔离到
@MainActor。 - 共享可变状态:将其置于
actor后,或仅在该状态由 UI 管理时使用@MainActor。 - 后台工作:当工作必须脱离调用方隔离时,使用标记为
@concurrent的asyncAPI;当工作可以安全地继承调用方隔离时,使用不带@concurrent的nonisolated。创建Task时,让入口隔离与其同步前缀相匹配。如果第一个await之前没有任何操作需要主 Actor,请使用Task { @concurrent in ... },并通过await MainActor.run { ... }切回以更新 UI。如果该前缀混合了简单的非主线程语句和主 Actor 工作,则保留继承的@MainActor起始隔离——为了将开销很小的一行代码移出主线程而额外切换一次并不值得。 - Sendable 问题:优先使用不可变值和显式边界,而非
@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 入口隔离
让 Task 的入口隔离与其同步前缀(从 { 到第一个 await 之间的所有内容)保持一致。
- 如果该前缀中的任何内容需要
@MainActor,则保留继承的@MainActor起始隔离。 - 如果该前缀中的内容都不需要
@MainActor,则优先使用Task { @concurrent in ... },仅在修改由 UI 管理的状态时切回主 Actor。
// ❌ Synchronous prefix is empty; first work hops away
Task {
await hopToOtherIsolationDomain()
}
// ❌ Synchronous prefix is only `print` (trivial, non-main); first await hops away
Task {
print("Also not main-thread-bound")
await hopToOtherIsolationDomain()
}
// ✅ Start off the main actor, hop back only for UI work
Task { @concurrent in
await hopToOtherIsolationDomain()
await MainActor.run { updateUI() }
}
// ✅ Synchronous prefix DOES contain main-actor work — keep inheritance
Task {
print("debug") // trivial, non-main — rides along
self.isLoading = true // needs @MainActor, before any await
await fetchData()
}
Swift 6 迁移快速指南
Swift 6 的关键变更:
- 默认启用严格并发检查
- 在编译时实现完整的数据竞争安全
- 在边界处强制执行 Sendable 要求
- 检查所有异步边界的隔离
迁移验证循环
每次迁移变更都应用以下循环:
- 构建——运行
swift build或 Xcode 构建,以发现新的诊断信息 - 修复——每次处理一类错误(例如,先解决所有 Sendable 问题)
- 重新构建——确认修复能够干净地通过编译,再继续
- 测试——运行测试套件以发现回归问题(
swift test或 Cmd+U) - 只有在解决所有诊断信息后,才继续处理下一个文件/模块
如果修复引入了新的警告,请先解决再继续。切勿批量处理多个无关的修复——保持提交小而易于审查。
详细迁移步骤请参阅 references/migration.md。
参考资料导航
打开与问题相匹配的最小范围参考资料:
- 基础
references/async-await-basics.md— async/await 语法、执行顺序、async let、URLSession 模式references/tasks.md— Task 生命周期、取消、优先级、任务组、结构化与非结构化并发references/actors.md— Actor 隔离、@MainActor、全局 Actor、可重入性、自定义执行器、Mutexreferences/sendable.md— Sendable 一致性、值类型/引用类型、@unchecked、区域隔离references/threading.md— 执行模型、挂起点、Swift 6.2 隔离行为
- 流
references/async-sequences.md— AsyncSequence、AsyncStream,以及何时使用它们而非常规异步方法references/async-algorithms.md— 防抖、节流、合并、combineLatest、通道、计时器
- 应用主题
references/testing.md— 优先使用 Swift Testing,后备使用 XCTest,泄漏检查references/performance.md— 使用 Instruments 进行性能分析、减少挂起点、执行策略references/memory-management.md— Task 中的循环引用、内存安全模式references/core-data.md— NSManagedObject 的可发送性、自定义执行器、隔离冲突references/observation.md— 与 @MainActor 一起使用 @Observable、跨隔离访问、Sendable 约束
- 迁移与工具
references/migration.md— Swift 6 迁移策略、将闭包转换为异步代码、@preconcurrency、FRP 迁移references/linting.md— 并发相关的 lint 规则和 SwiftLintasync_without_await
- 术语表
references/glossary.md— 并发核心术语的简明定义
验证清单
更改并发代码时:
- 重新检查构建设置,然后再解读诊断信息。
- 每次先构建并清除一类错误,再继续处理其他问题。不要将无关修复合并到同一次更改中。
- 运行测试,尤其是对 actor、生命周期和取消敏感的测试。
- 如需声明性能表现,请使用 Instruments,而不是猜测。
- 验证长时间运行的任务是否会释放资源并正确响应取消。
- 在长时间运行的操作中检查
Task.isCancelled。 - 在异步上下文中,绝不要使用信号量或临时加锁;使用 actor 隔离或
Mutex表达所有权会更安全。
注:此技能基于 Antoine van der Lee 编写的综合版 Swift Concurrency Course。
评论
支持 Markdown