mcpField
S
swift-concurrency

swift-concurrency

github.com/wendylabsinc開発6274 回インストール6 週間前に更新
リポジトリ

swift-concurrency について

swift-concurrencyは、Swiftの並行コードの作成と移行を支援するスキルです。隔離、構造化並行処理、Sendable、テスト、性能、Swift 6への移行を、プロジェクト設定に応じて扱います。

主な機能

  1. 01async/awaitとタスクのパターンを適用
  2. 02アクターと隔離境界を分析
  3. 03Sendableとデータ競合安全性に対応
  4. 04並行処理エラーとlint警告を診断
  5. 05Swift 6移行と検証を計画

活用シーン

  1. 01並列処理に適したパターンの選択
  2. 02隔離またはSendableのコンパイルエラー修正
  3. 03非同期テストと性能問題の確認
  4. 04Swiftプロジェクトを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 パターン、アクター、タスク、Sendable への適合、Swift 6 への移行を扱います。開発者が安全で高性能な並行コードを記述し、Swift の構造化並行性モデルの複雑さに対応できるよう支援するために、このスキルを使用してください。

エージェントの行動規範(以下のルールに従ってください)

  1. 助言がプロジェクト設定に依存する場合は、プロジェクトまたはパッケージのファイルを分析し、使用されている Swift 言語モード(Swift 5.x または Swift 6)と Xcode/Swift ツールチェーンを確認してください。
  2. 修正を提案する前に、分離境界を特定してください。@MainActor、カスタムアクター、アクターインスタンスの分離、または非分離のいずれかです。
  3. 一律の解決策として @MainActor を推奨しないでください。そのコードにメインアクター分離が適切な理由を説明してください。
  4. 非構造化タスクより、構造化並行性(子タスク、タスクグループ)を優先してください。明確な理由がある場合に限り、Task.detached を使用してください。
  5. @preconcurrency、@unchecked Sendable、または nonisolated(unsafe) を推奨する場合は、以下を必須としてください。
    • 文書化された安全性の不変条件
    • 削除または移行するためのフォローアップチケット
  6. 移行作業では、影響範囲を最小限に抑え(小規模でレビューしやすい変更にし)、検証手順を追加してください。

プロジェクト設定の確認(助言の前に評価してください)

並行性の挙動はビルド設定に依存します。必ず次の項目の確認を試みてください。

  • デフォルトのアクター分離(モジュールのデフォルトは @MainActor ですか、それとも nonisolated ですか?)
  • 厳密な並行性チェックのレベル(minimal/targeted/complete)
  • 今後の機能が有効になっているか(特に NonisolatedNonsendingByDefault)
  • Swift 言語モード(Swift 5.x または Swift 6)と SwiftPM のツールバージョン
手動での確認(スクリプト不要)
  • SwiftPM:
    • .defaultIsolation(MainActor.self) があるか Package.swift を確認してください。
    • .enableUpcomingFeature("NonisolatedNonsendingByDefault") があるか Package.swift を確認してください。
    • 厳密な並行性に関するフラグを確認してください。.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(アクター、@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 の動作変更)を参照します
  • 「メインアクターで隔離された ... は、非隔離コンテキストから使用できません」
    • まず、本当に @MainActor に属するべきか判断します
    • 次に、references/actors.md(グローバルアクター、nonisolated、隔離パラメーター)と references/threading.md(デフォルトの隔離)を参照します
  • 「クラスプロパティ『current』は非同期コンテキストから使用できません」(Thread API)
    • スレッド中心のデバッグを避け、隔離と Instruments に頼るため、references/threading.md を参照します
  • 「wait(...) は非同期コンテキストから使用できません」のような XCTest の非同期エラー
    • 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)

Task Group - 構造化並行処理で動的な並行処理を行う

// 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、グローバルアクター、再入可能性、カスタムエグゼキューター、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 を参照してください。

コメント