关于 swift-concurrency
swift-concurrency 是为 Swift 并发代码开发和迁移提供指导的技能,涵盖隔离、结构化并发、Sendable、测试、性能及 Swift 6 迁移,并结合项目设置给出建议。
主要功能
- 01运用 async/await 与任务模式
- 02分析 Actor 和隔离边界
- 03处理 Sendable 与数据竞争安全
- 04诊断并发错误和 lint 警告
- 05规划 Swift 6 迁移与验证
适用场景
- 01为并行工作选择并发模式
- 02修复隔离或 Sendable 编译错误
- 03检查异步测试与性能问题
- 04将 Swift 项目迁移到 Swift 6
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wendylabsinc/ claude-skills --skill swift-concurrency安装后在 Claude Code 中输入/swift-concurrency确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/actors.mdasync-await-basics.mdasync-sequences.mdglossary.mdperformance.mdproduction-patterns.mdsendable.mdtasks.mdtesting.mdthreading.md详细介绍
Swift Concurrency
概述
此技能提供有关 Swift Concurrency 的专业指导,涵盖现代 async/await 模式、actors、tasks、Sendable 一致性,以及迁移到 Swift 6。使用此技能可帮助开发者编写安全、高性能的并发代码,并应对 Swift 结构化并发模型的复杂性。
Agent 行为约定(遵循以下规则)
- 如果建议取决于项目配置,请分析项目或包文件,确认使用的 Swift 语言模式(Swift 5.x 还是 Swift 6)以及 Xcode/Swift 工具链。
- 提出修复方案前,先确定隔离边界:
@MainActor、自定义 actor、actor 实例隔离或 nonisolated。 - 不要将
@MainActor作为通用修复方案。应说明为何代码适合由主 actor 隔离。 - 优先采用结构化并发(子任务、任务组),而非非结构化任务。只有明确说明理由时才使用
Task.detached。 - 如果建议使用
@preconcurrency、@unchecked Sendable或nonisolated(unsafe),必须要求:- 记录安全性不变量
- 创建后续任务,以便移除或迁移相关代码
- 对于迁移工作,应尽量缩小影响范围(采用小而易于审查的改动),并添加验证步骤。
项目设置检查(提供建议前评估)
并发行为取决于构建设置。务必尝试确认:
- 默认 actor 隔离(模块默认是
@MainActor还是nonisolated?) - 严格并发检查级别(minimal/targeted/complete)
- 是否启用了即将推出的功能(尤其是
NonisolatedNonsendingByDefault) - Swift 语言模式(Swift 5.x 还是 Swift 6)和 diSwiftPM 工具版本
手动检查(无需脚本)
- SwiftPM:
- 检查
Package.swift中是否有.defaultIsolation(MainActor.self)。 - 检查
Package.swift中是否有.enableUpcomingFeature("NonisolatedNonsendingByDefault")。 - 检查是否有严格并发标志:
.enableExperimentalFeature("StrictConcurrency=targeted")(或类似项)。 - 检查文件顶部的工具版本:
// swift-tools-version: ...
- 检查
- Xcode 项目:
- 在
project.pbxproj中搜索:SWIFT_DEFAULT_ACTOR_ISOLATIONSWIFT_STRICT_CONCURRENCYSWIFT_UPCOMING_FEATURE_(和/或SWIFT_ENABLE_EXPERIMENTAL_FEATURES)
- 在
如果上述任何信息未知,请先让开发者确认,再提供可能影响迁移的指导。
快速决策树
开发者需要并发方面的指导时,请遵循以下决策树:
-
刚开始编写 async 代码?
- 阅读
references/async-await-basics.md,了解基础模式 - 对于并行操作 → 查看
references/tasks.md(async let、任务组)
- 阅读
-
需要保护共享的可变状态?
- 需要保护基于类的状态 → 查看
references/actors.md(actors、@MainActor) - 需要线程安全地传递值 → 查看
references/sendable.md(Sendable 一致性)
- 需要保护基于类的状态 → 查看
-
需要管理异步操作?
- 结构化异步工作 → 查看
references/tasks.md(Task、子任务、取消) - 流式数据 → 查看
references/async-sequences.md(AsyncSequence、AsyncStream)
- 结构化异步工作 → 查看
-
遇到性能或调试问题?
- 异步代码运行缓慢 → 查看
references/performance.md(性能分析、挂起点) - 测试方面的问题 → 查看
references/testing.md(XCTest、Swift Testing)
- 异步代码运行缓慢 → 查看
先分诊的操作手册(常见错误 -> 下一步最佳操作)
- SwiftLint 并发相关警告
- 使用
references/linting.md了解规则意图和首选修复方式;避免用虚假的 await 作为“修复”。
- 使用
- SwiftLint
async_without_await警告- 如果不需要
async,就移除它;如果因协议/重写/@concurrent 而必须保留,优先使用范围较小的抑制,而不是添加虚假的 await。参见references/linting.md。
- 如果不需要
- “发送非 Sendable 类型的值……可能导致数据竞争”
- 首先:确定该值在哪里跨越隔离边界
- 然后:使用
references/sendable.md和references/threading.md(特别关注 Swift 6.2 的行为变化)
- “主 Actor 隔离的……无法在非隔离上下文中使用”
- 首先:判断它是否确实应属于
@MainActor - 然后:使用
references/actors.md(全局 Actor、nonisolated、隔离参数)和references/threading.md(默认隔离)
- 首先:判断它是否确实应属于
- “异步上下文中无法使用类属性 ‘current’”(Thread API)
- 使用
references/threading.md,避免以线程为中心进行调试,改用隔离机制和 Instruments
- 使用
- XCTest 异步错误,例如“异步上下文中无法使用 wait(...)”
- 使用
references/testing.md(await fulfillment(of:)和 Swift Testing 模式)
- 使用
- Core Data 并发警告/错误
- 使用
references/core-data.md(DAO/NSManagedObjectID、默认隔离冲突)
- 使用
核心模式参考
何时使用各种并发工具
async/await - 将现有同步代码改为异步
// Use for: Single asynchronous operations
func fetchUser() async throws -> User {
try await networkClient.get("/user")
}
async let - 并行运行多个独立的异步操作
// Use for: Fixed number of parallel operations known at compile time
async let user = fetchUser()
async let posts = fetchPosts()
let profile = try await (user, posts)
任务组 - 使用结构化并发执行动态并行操作
// Use for: Unknown number of parallel operations at compile time
await withTaskGroup(of: Result.self) { group in
for item in items {
group.addTask { await process(item) }
}
}
Actor - 保护可变状态,避免数据竞争
// Use for: Shared mutable state accessed from multiple contexts
actor DataCache {
private var cache: [String: Data] = [:]
func get(_ key: String) -> Data? { cache[key] }
}
@MainActor - 确保在主线程上更新 UI
// Use for: View models, UI-related classes
@MainActor
class ViewModel: ObservableObject {
@Published var data: String = ""
}
常见场景
场景:多个并行网络请求
async let users = fetchUsers()
async let posts = fetchPosts()
async let comments = fetchComments()
let (u, p, c) = try await (users, posts, comments)
场景:并行处理数组项
await withTaskGroup(of: ProcessedItem.self) { group in
for item in items {
group.addTask { await process(item) }
}
for await result in group {
results.append(result)
}
}
Swift 6 迁移速查指南
Swift 6 的主要变化:
- 默认启用严格并发检查
- 在编译时确保完全的数据竞争安全
- 在边界处强制执行 Sendable 要求
- 对所有异步边界进行隔离检查
详细迁移步骤请参见 references/migration.md。
参考文件
根据需要加载以下文件,了解特定主题:
async-await-basics.md- async/await 语法、执行顺序、async let、URLSession 模式tasks.md- Task 生命周期、取消、优先级、任务组、结构化与非结构化并发threading.md- 线程/任务关系、挂起点、隔离域、nonisolatedactors.md- Actor 隔离、@MainActor、全局 Actor、可重入性、自定义执行器、Mutexsendable.md- Sendable 一致性、值类型/引用类型、@unchecked、区域隔离async-sequences.md- AsyncSequence、AsyncStream、何时使用以及何时改用常规异步方法performance.md- 使用 Instruments 进行性能分析、减少挂起点、执行策略testing.md- XCTest 异步模式、Swift Testing、并发测试工具production-patterns.md- 高级模式:Actor 状态机、~Copyable 状态、结构化并发、无锁原子操作、服务生命周期
最佳实践摘要
- 优先使用结构化并发 - 尽可能使用任务组,而非非结构化任务
- 尽量减少挂起点 - 缩小 Actor 隔离代码段,以减少上下文切换
- 谨慎使用 @MainActor - 仅用于真正与 UI 相关的代码
- 让类型符合 Sendable - 通过符合 Sendable,支持安全的并发访问
- 处理取消 - 在长时间运行的操作中检查 Task.isCancelled
- 避免阻塞 - 切勿在异步上下文中使用信号量或锁
- 测试并发代码 - 使用适当的异步测试方法,并考虑时序问题
使用结构化并发管理资源
管理需要清理的资源(连接、文件句柄等)时,请使用 withX 模式,通过结构化并发确保正确清理。
不佳 — 非结构化清理:
func withConnection<R>(
_ body: (Connection) async throws -> R
) async throws -> R {
let connection = Connection()
try await connection.connect()
defer {
Task {
await connection.disconnect() // Unstructured! May not complete
}
}
return try await body(connection)
}
更好 — 在所有路径中显式清理:
func withConnection<R>(
_ body: (Connection) async throws -> R
) async throws -> R {
let connection = Connection()
try await connection.connect()
do {
let result = try await body(connection)
await connection.disconnect()
return result
} catch {
await connection.disconnect()
throw error
}
}
最佳 — 让这种模式无法被误用:
actor Client {
private let connection: Connection // Non-optional, always valid
private init(connection: Connection) {
self.connection = connection
}
static func withConnection<R>(
_ body: (Client) async throws -> R
) async throws -> R {
let connection = try await Connection.establish()
let client = Client(connection: connection)
do {
let result = try await body(client)
await connection.close()
return result
} catch {
await connection.close()
throw error
}
}
// No public connect()/disconnect() methods - can't forget cleanup
}
这种模式:
- 将初始化器设为私有,强制用户通过
withConnection操作 - 将资源存储为非可选属性(无需使用 guard-let)
- 确保函数返回前完成清理
- 完全消除
notConnected错误情况
Swift 6 严格并发模式下的全局可变状态
在 Swift 6 严格并发模式下访问 stdout 等 C 全局变量时,请使用局部 nonisolated(unsafe) 变量:
@inline(__always)
private func flushStdout() {
nonisolated(unsafe) let out = stdout
fflush(out)
}
nonisolated(unsafe) 注解会告知编译器,跳过对这次特定 C 全局变量访问的并发检查。
使用 ThrowingDiscardingTaskGroup 执行即发即弃任务
需要启动多个任务,但不需要收集各自的结果时,请使用 withThrowingDiscardingTaskGroup:
try await withThrowingDiscardingTaskGroup { taskGroup in
taskGroup.addTask {
try await handleClient(client1)
}
taskGroup.addTask {
try await handleClient(client2)
}
}
优雅关闭模式
在长时间运行的循环中,同时检查取消和优雅关闭:
while !Task.isShuttingDownGracefully && !Task.isCancelled {
try await handle(value)
}
// Use gracefulShutdown() for clean termination
try await gracefulShutdown()
AsyncStream:尽可能避免使用
AsyncStream 不支持背压。 如果生产者的生成速度快于消费者的处理速度,值会在无界缓冲区中不断累积,导致内存增长。
优先映射 NIOAsyncSequence 或其他支持背压的序列:
// Bad - no backpressure, unbounded buffer
let stream = AsyncStream<Message> { continuation in
Task {
for await rawMessage in nioChannel.inbound {
continuation.yield(Message(rawMessage))
}
continuation.finish()
}
}
// Good - preserves backpressure from NIO
let messages = nioChannel.inbound.map { Message($0) }
for try await message in messages {
process(message)
}
如果必须使用 AsyncStream,请通过 makeStream() 使用结构化并发:
func run() async {
let (stream, continuation) = AsyncStream<Value>.makeStream()
async let producer: Void = runProducer(continuation: continuation)
for await value in stream {
process(value)
}
await producer
}
private func runProducer(
continuation: sending AsyncStream<Value>.Continuation
) async {
let cont = consume continuation // Transfer ownership
try? await withThrowingDiscardingTaskGroup { group in
group.addTask {
while !Task.isCancelled {
cont.yield(produceValue())
try await Task.sleep(for: .seconds(1))
}
}
}
cont.finish()
}
要点:
sending参数会将所有权转移给函数consume continuation可在任务组的子任务之间安全共享AsyncStream.Continuation是Sendable,无需使用nonisolated(unsafe)- 避免在
AsyncStream.init中使用Task { },这属于非结构化并发
验证清单(修改并发代码时)
- 在解读诊断信息前,确认构建设置(默认隔离、严格并发、即将推出的功能)。
- 重构后:
- 运行测试,尤其是对并发敏感的测试(参见
references/testing.md)。 - 如果涉及性能,请使用 Instruments 验证(参见
references/performance.md)。 - 如果涉及生命周期,请使用
with风格的 API 验证代码结构是否正确。
- 运行测试,尤其是对并发敏感的测试(参见
术语表
请参阅 references/glossary.md,了解本技能中使用的核心并发术语的简要定义。