mcpField
S
swift-concurrency

swift-concurrency

github.com/wendylabsinc开发6274 次安装6 周前更新
代码仓库

关于 swift-concurrency

swift-concurrency 是为 Swift 并发代码开发和迁移提供指导的技能,涵盖隔离、结构化并发、Sendable、测试、性能及 Swift 6 迁移,并结合项目设置给出建议。

主要功能

  1. 01运用 async/await 与任务模式
  2. 02分析 Actor 和隔离边界
  3. 03处理 Sendable 与数据竞争安全
  4. 04诊断并发错误和 lint 警告
  5. 05规划 Swift 6 迁移与验证

适用场景

  1. 01为并行工作选择并发模式
  2. 02修复隔离或 Sendable 编译错误
  3. 03检查异步测试与性能问题
  4. 04将 Swift 项目迁移到 Swift 6

安装

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

$npx skills add https://github.com/wendylabsinc/claude-skills --skill swift-concurrency

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

包含的文件

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

references/
actors.md
async-await-basics.md
async-sequences.md
glossary.md
performance.md
production-patterns.md
sendable.md
tasks.md
testing.md
threading.md

详细介绍

Swift Concurrency

概述

此技能提供有关 Swift Concurrency 的专业指导,涵盖现代 async/await 模式、actors、tasks、Sendable 一致性,以及迁移到 Swift 6。使用此技能可帮助开发者编写安全、高性能的并发代码,并应对 Swift 结构化并发模型的复杂性。

Agent 行为约定(遵循以下规则)

  1. 如果建议取决于项目配置,请分析项目或包文件,确认使用的 Swift 语言模式(Swift 5.x 还是 Swift 6)以及 Xcode/Swift 工具链。
  2. 提出修复方案前,先确定隔离边界:@MainActor、自定义 actor、actor 实例隔离或 nonisolated。
  3. 不要将 @MainActor 作为通用修复方案。应说明为何代码适合由主 actor 隔离。
  4. 优先采用结构化并发(子任务、任务组),而非非结构化任务。只有明确说明理由时才使用 Task.detached。
  5. 如果建议使用 @preconcurrency、@unchecked Sendable 或 nonisolated(unsafe),必须要求:
    • 记录安全性不变量
    • 创建后续任务,以便移除或迁移相关代码
  6. 对于迁移工作,应尽量缩小影响范围(采用小而易于审查的改动),并添加验证步骤。

项目设置检查(提供建议前评估)

并发行为取决于构建设置。务必尝试确认:

  • 默认 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_ISOLATION
      • SWIFT_STRICT_CONCURRENCY
      • SWIFT_UPCOMING_FEATURE_(和/或 SWIFT_ENABLE_EXPERIMENTAL_FEATURES)

如果上述任何信息未知,请先让开发者确认,再提供可能影响迁移的指导。

快速决策树

开发者需要并发方面的指导时,请遵循以下决策树:

  1. 刚开始编写 async 代码?

    • 阅读 references/async-await-basics.md,了解基础模式
    • 对于并行操作 → 查看 references/tasks.md(async let、任务组)
  2. 需要保护共享的可变状态?

    • 需要保护基于类的状态 → 查看 references/actors.md(actors、@MainActor)
    • 需要线程安全地传递值 → 查看 references/sendable.md(Sendable 一致性)
  3. 需要管理异步操作?

    • 结构化异步工作 → 查看 references/tasks.md(Task、子任务、取消)
    • 流式数据 → 查看 references/async-sequences.md(AsyncSequence、AsyncStream)
  4. 遇到性能或调试问题?

    • 异步代码运行缓慢 → 查看 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 - 线程/任务关系、挂起点、隔离域、nonisolated
  • actors.md - Actor 隔离、@MainActor、全局 Actor、可重入性、自定义执行器、Mutex
  • sendable.md - Sendable 一致性、值类型/引用类型、@unchecked、区域隔离
  • async-sequences.md - AsyncSequence、AsyncStream、何时使用以及何时改用常规异步方法
  • performance.md - 使用 Instruments 进行性能分析、减少挂起点、执行策略
  • testing.md - XCTest 异步模式、Swift Testing、并发测试工具
  • production-patterns.md - 高级模式:Actor 状态机、~Copyable 状态、结构化并发、无锁原子操作、服务生命周期

最佳实践摘要

  1. 优先使用结构化并发 - 尽可能使用任务组,而非非结构化任务
  2. 尽量减少挂起点 - 缩小 Actor 隔离代码段,以减少上下文切换
  3. 谨慎使用 @MainActor - 仅用于真正与 UI 相关的代码
  4. 让类型符合 Sendable - 通过符合 Sendable,支持安全的并发访问
  5. 处理取消 - 在长时间运行的操作中检查 Task.isCancelled
  6. 避免阻塞 - 切勿在异步上下文中使用信号量或锁
  7. 测试并发代码 - 使用适当的异步测试方法,并考虑时序问题
使用结构化并发管理资源

管理需要清理的资源(连接、文件句柄等)时,请使用 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,了解本技能中使用的核心并发术语的简要定义。

评论

支持 Markdown