mcpField
S
swift-concurrency

swift-concurrency

github.com/avdlee开发1.7k17.1k 次安装6 周前更新
代码仓库

关于 swift-concurrency

swift-concurrency 是为 Swift 开发者提供并发诊断与迁移指导的技能,涵盖隔离、Sendable、任务和测试。它强调先核对项目设置与具体诊断,再采用尽可能小且安全的修复。

主要功能

  1. 01分析 Swift 并发构建设置
  2. 02定位隔离边界与并发诊断
  3. 03提供常见错误的修复指引
  4. 04按主题路由到参考文档
  5. 05指导 Swift 6 迁移与验证

适用场景

  1. 01排查 MainActor 隔离错误
  2. 02解决 Sendable 数据传递问题
  3. 03规划 Swift 6 并发迁移
  4. 04验证并发修改的构建与测试

安装

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

$npx skills add https://github.com/avdlee/swift-concurrency-agent-skill --skill swift-concurrency

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

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

agents/
openai.yaml
references/
_index.md
actors.md
async-algorithms.md
async-await-basics.md
async-sequences.md
core-data.md
glossary.md
linting.md
memory-management.md
migration.md
observation.md
performance.md
sendable.md
tasks.md
testing.md
threading.md

详细介绍

Swift 并发

快速路径

提出修复方案前:

  1. 分析 Package.swift 或 .pbxproj,确定 Swift 语言模式、严格并发级别、默认隔离和即将启用的功能。始终执行此操作,不要只在迁移工作中执行。
  2. 获取准确的诊断信息和出问题的符号。
  3. 确定隔离边界:@MainActor、自定义 Actor、Actor 实例隔离或 nonisolated。
  4. 确认代码是否与 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_awaitasync 是否确实是协议、重写或 @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

快速修复无效时

  1. 如果尚未确认,请收集项目设置。
  2. 重新评估该类型跨越了哪些隔离边界。
  3. 转至对应的参考文件,深入修复。
  4. 如果修复可能改变行为,请记录不变条件并添加验证步骤。

最小安全修复

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

  • 与 UI 相关的状态:将类型或成员隔离到 @MainActor。
  • 共享可变状态:将其置于 actor 后,或仅在该状态由 UI 管理时使用 @MainActor。
  • 后台工作:当工作必须脱离调用方隔离时,使用标记为 @concurrent 的 async API;当工作可以安全地继承调用方隔离时,使用不带 @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 要求
  • 检查所有异步边界的隔离
迁移验证循环

每次迁移变更都应用以下循环:

  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,以及何时使用它们而非常规异步方法
    • 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 规则和 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 Concurrency Course。

评论

支持 Markdown