SKILL.md
readonly只读
name
swift-concurrency
description
诊断 Swift 并发问题,将基于回调的代码重构为 async/await,并在处理任务、Actor、@MainActor、Sendable、数据竞争、线程安全或与并发相关的编译器和 linter 警告时指导 Swift 6 迁移。
Swift 并发
快速路径
在提出修复方案之前:
- 分析
Package.swift或.pbxproj以确定 Swift 语言模式、严格并发级别、默认隔离和即将推出的功能。始终执行此操作,而不仅仅是迁移工作。 - 捕获确切的诊断信息和有问题的符号。
- 确定隔离边界:
@MainActor、自定义 Actor、Actor 实例隔离或nonisolated。 - 确认代码是否与 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 = 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,或仅在主 Actor 所有权正确时使用 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 |
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启动——将廉价的行拆分到主 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 要求在边界强制执行
- 所有异步边界的隔离检查
迁移验证循环
对每个迁移更改应用此循环:
- 构建 — 运行
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、何时使用 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 规则和 SwiftLintasync_without_await
- 词汇表
references/glossary.md— 核心并发术语的快速定义
验证检查清单
更改并发代码时:
- 在解释诊断之前重新检查构建设置。
- 构建并清除一个错误类别后再继续。不要将不相关的修复批处理到同一更改中。
- 运行测试,特别是 Actor、生命周期和取消敏感测试。
- 使用 Instruments 进行性能声明,而不是猜测。
- 验证长时间运行任务的释放和取消行为。
- 在长时间运行的操作中检查
Task.isCancelled。 - 在异步上下文中,当 Actor 隔离或
Mutex可以更安全地表达所有权时,切勿使用信号量或临时锁定。
注意:此技能基于 Antoine van der Lee 的全面 Swift 并发课程。






