mcpField
S
swift-concurrency

swift-concurrency

github.com/dpearson2699開発1.3k4.3k 回インストール13 週間前に更新
リポジトリ

swift-concurrency について

Swift 6.3以降を対象とする並行処理スキルで、Swift開発者によるコードのレビュー、修正、作成を支援します。隔離とSendableの安全性を重視し、動作変更を最小限に抑える方法を示します。

主な機能

  1. 01並行処理コードのレビューと修正
  2. 02Actor隔離の指針
  3. 03Sendable安全性の指針
  4. 04構造化並行処理とキャンセル
  5. 05AsyncSequenceとAsyncStreamのパターン
  6. 06Swift 6.2の並行処理変更

活用シーン

  1. 01並行処理のコンパイラーエラー診断
  2. 02データ競合の安全性問題の修正
  3. 03Swift並行処理設定のレビュー
  4. 04タスクとキャンセル方式の選択

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swift-concurrency

インストール後、Claude Code で/swift-concurrencyを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

evals/
evals.json
references/
approachable-concurrency.md
async-algorithms.md
bridging-interop.md
concurrency-patterns.md
diagnostics.md
swiftui-concurrency.md
synchronization-primitives.md

詳細

Swift Concurrency

Swift 6.3 以降を対象に、並行処理を行う Swift コードのレビュー、修正、作成を行います。Swift 6.4/Xcode 27 ベータ版のクリーンアップ API は、ツールチェーンと利用可能性を明示的に確認したうえで使用してください。アクター分離、Sendable の安全性、最新の並行処理パターンを、動作を最小限しか変更せずに適用します。

目次

トリアージのワークフロー

並行処理の問題を診断する際は、次の手順に従ってください。

手順 1:コンテキストを把握する
  • コンパイラの診断メッセージと、問題のあるシンボルを正確にコピーします。
  • プロジェクトの並行処理設定を確認します。
    • Swift の言語バージョン(6.2 以降である必要があります)。
    • バージョン固有の機能やリリースノートの回避策を確認するための Xcode/ツールチェーンのバージョン。
    • Approachable Concurrency が有効かどうか。
    • Default Actor Isolation が MainActor に設定されているかどうか。
    • Swift 6 の厳密な並行処理の状態:Swift 6 言語モードでは Complete/errors、Swift 5 の移行設定を監査する場合に限り Complete/Targeted/Minimal。
  • コードの現在のアクターコンテキスト(@MainActor、カスタムの actor、nonisolated)と、デフォルトの分離モードが有効かどうかを特定します。
  • コードが UI に依存するものか、メインアクター以外で実行することを意図したものか確認します。
手順 2:最小限の安全な修正を適用する

データ競合の安全性を満たしながら、既存の動作を維持できる修正を優先します。

状況推奨される修正
UI に依存する型型または該当するメンバーに @MainActor を付与します。
MainActor 型のプロトコル適合分離された適合を使用します:extension Foo: @MainActor Proto。
グローバル/静的状態@MainActor で保護するか、アクターに移します。
バックグラウンド処理が必要nonisolated 型の @concurrent 非同期関数を使用します。
Sendable エラー不変の値型を優先します。適切な場合にのみ Sendable を追加します。
分離境界を越えるコールバックより細かく制御するには sending パラメーター(SE-0430)を使用します。
手順 3:確認する
  • 再ビルドし、診断メッセージが解消されたことを確認します。
  • 修正によって新たな警告が発生していないか確認します。
  • 不要な @unchecked Sendable や nonisolated(unsafe) が追加されていないことを確認します。
  • ビルド設定のレビューでは、設定と最小限のコードレベルの対処だけで止めます。診断や移行を求められていない限り、Thread Sanitizer、広範な移行の順序付け、アーキテクチャに関する助言は追加しないでください。

Swift 6.2 の言語変更

Swift 6.2 では、「approachable concurrency」と呼ばれる一連の言語変更が導入されました。これにより、注釈の負担を軽減しながら、並行処理コードをデフォルトでより安全に記述できます。Xcode では、Approachable Concurrency と Default Actor Isolation は別々のビルド設定です。同梱の upcoming-feature フラグを有効にするには Approachable Concurrency を使用し、注釈のないコードを @MainActor として推論させるには Default Actor Isolation を MainActor に設定します。

SE-0466:デフォルトの MainActor 分離

-default-isolation MainActor コンパイラーフラグ、SwiftPM の .defaultIsolation(MainActor.self)、または Xcode の Default Actor Isolation 設定を MainActor にすると、明示的にオプトアウトしない限り、モジュール内の注釈のない宣言は @MainActor として推論されます。

効果: あらゆる箇所に @MainActor を記述しなくても、UI に依存するコードやグローバル/静的状態で発生するデータ競合安全性のエラーの大半を解消できます。

// With default MainActor isolation enabled, these are implicitly @MainActor:
final class StickerLibrary {
    static let shared = StickerLibrary()  // safe -- on MainActor
    var stickers: [Sticker] = []
}

final class StickerModel {
    let photoProcessor = PhotoProcessor()
    var selection: [PhotosPickerItem] = []
}

// Conformances are also implicitly isolated:
extension StickerModel: Exportable {
    func export() {
        photoProcessor.exportAsPNG()
    }
}

使用する場面: 大半のコードが UI に依存するアプリ、スクリプト、その他の実行可能ターゲットに推奨されます。アクターに依存しない状態を保つべきライブラリーターゲットには推奨されません。

SE-0461: nonisolated(nonsending)

nonisolated な非同期関数は、グローバル並行エグゼキューターへホップする代わりに、デフォルトで呼び出し元のアクター上にとどまるようになりました。これが nonisolated(nonsending) の動作です。

class PhotoProcessor {
    func extractSticker(data: Data, with id: String?) async -> Sticker? {
        // In Swift 6.2+, this runs on the caller's actor (e.g., MainActor)
        // instead of hopping to a background thread.
        // ...
    }
}

@MainActor
final class StickerModel {
    let photoProcessor = PhotoProcessor()

    func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {
        guard let data = try await item.loadTransferable(type: Data.self) else {
            return nil
        }
        // No data race -- photoProcessor stays on MainActor
        return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
    }
}

必要に応じてバックグラウンド実行を明示的に要求するには、@concurrent を使用します。

@concurrent 属性

@concurrent を指定すると、関数は常に並行スレッドプールで実行され、呼び出し元のアクターはほかのタスクを実行できるようになります。

class PhotoProcessor {
    var cachedStickers: [String: Sticker] = [:]

    func extractSticker(data: Data, with id: String) async -> Sticker {
        if let sticker = cachedStickers[id] { return sticker }

        let sticker = await Self.extractSubject(from: data)
        cachedStickers[id] = sticker
        return sticker
    }

    @concurrent
    static func extractSubject(from data: Data) async -> Sticker {
        // Expensive image processing -- runs on background thread pool
        // ...
    }
}

関数をバックグラウンドスレッドへ移すには、次の両方のオプトアウトを指定します。

  1. 含まれる型を nonisolated にするか、関数を nonisolated コンテキストから呼び出せるようにします。
  2. オフロードする関数に @concurrent を追加します。nonisolated だけでは、CPU 負荷の高い処理は呼び出し元のアクターから移動しません。
  3. まだ非同期でない場合は、async を追加します。
  4. 呼び出し箇所に await を追加します。
nonisolated struct PhotoProcessor {
    @concurrent
    func process(data: Data) async -> ProcessedPhoto? { /* ... */ }
}

// Caller:
processedPhotos[item.id] = await PhotoProcessor().process(data: data)
SE-0472: Task.immediate

Task.immediate は、キューに追加されるのではなく、中断ポイントに達する前に現在のアクター上で同期的に実行を開始します。

Task.immediate { await handleUserInput() }

遅延なく開始する必要がある、レイテンシーに敏感な処理に使用します。即時実行開始と detached セマンティクスを組み合わせた Task.immediateDetached もあります。

SE-0475: トランザクション型の監視(Observations)

Observations { } は、AsyncSequence を介して @Observable 型を非同期に監視し、トランザクション型の変更追跡を可能にします。

for await _ in Observations { model.count } {
    print("Count changed to \(model.count)")
}
隔離された適合

MainActor の状態を必要とする適合は、隔離された適合と呼ばれます。コンパイラーは、一致する隔離コンテキストでのみ使用されることを保証します。

protocol Exportable {
    func export()
}

// Isolated conformance: only usable on MainActor
extension StickerModel: @MainActor Exportable {
    func export() {
        photoProcessor.exportAsPNG()
    }
}

@MainActor
struct ImageExporter {
    var items: [any Exportable]

    mutating func add(_ item: StickerModel) {
        items.append(item)  // OK -- ImageExporter is on MainActor
    }
}

ImageExporter が nonisolated だった場合、StickerModel を追加すると、次のエラーが発生します。 「StickerModel の Exportable への Main actor 隔離された適合は、nonisolated コンテキストでは使用できません。」

クロックエポック

ContinuousClock と SuspendingClock は .epoch(SE-0473)を公開するようになり、クロック型間で即座に比較や変換を行えるようになりました。

let continuous = ContinuousClock()
let elapsed = continuous.now - continuous.epoch  // Duration since system boot

アクター隔離のルール

  1. 変更可能な共有状態はすべて、アクターまたはグローバルアクターで保護する必要があります。
  2. UI に触れるコードにはすべて @MainActor を使用します。例外はありません。 グローバルアクターもアクター隔離の一種です。UI に紐づく共有状態には、標準パターンとして @MainActor を使用します。
  3. nonisolated は、不変(let)プロパティにアクセスするメソッド、または純粋な計算を行うメソッドにのみ使用します。
  4. 呼び出し元のアクターから処理を明示的に移動するには、@concurrent を使用します。
  5. データ競合が起きないことを証明し、より安全な代替手段を検討し尽くしていない限り、nonisolated(unsafe) は使用しないでください。これは安全でないコードを監査するための境界であり、同期プリミティブではありません。同期型並列ループでの、限定的な unsafe ポインターキャプチャパターンについては、ブリッジングと相互運用に記載された証明要件に従ってください。 各反復でアクセス対象が重複しない場合、競合する並行アクセスと同期の必要性をなくせることがあります。アクセス対象が重複しないこと自体は、同期ではありません。
  6. アクター内で手動ロック(NSLock、DispatchSemaphore)を使用しないでください。

Sendable のルール

  1. 値型(構造体、列挙型)は、保存プロパティがすべて Sendable であれば、自動的に Sendable になります。 変更可能な参照型に関する診断が出た場合は、参照を共有するのではなく、まず不変の Sendable 値スナップショットまたは DTO を切り出してください。
  2. アクターは暗黙的に Sendable です。
  3. @MainActor クラスは暗黙的に Sendable です。冗長な Sendable 適合を追加しないでください。
  4. 非アクタークラスは、final であり、すべての保存プロパティが let かつ Sendable である必要があります。
  5. @unchecked Sendable は最後の手段です。コンパイラーが安全性を証明できない理由を記述してください。
  6. より細かな隔離制御には、sending パラメーター(SE-0430)を使用します。
  7. @preconcurrency import は、変更できないサードパーティライブラリにのみ使用します。削除する計画を立ててください。

構造化並行処理のパターン

Async Defer(Swift 6.4+)

非同期コンテキストのdeferブロックでは、Swift 6.4+(SE-0493)でawaitを使用できます。 接続のクローズ、バッファーのフラッシュ、非同期呼び出しが必要なリソースの解放など、非同期のクリーンアップに使用します。

defer本体は周囲の隔離を引き継ぎ、スコープの終了時に暗黙的にawaitされます。キャンセルは抑制されません。Task.isCancelledまたはTask.checkCancellation()を確認するクリーンアップでは、引き続きキャンセルが検出されます。

func fetchData() async throws -> Data {
    let connection = try await openConnection()
    defer { await connection.close() }
    return try await connection.read()
}

Task: 非構造化タスクで、呼び出し元のコンテキストを引き継ぎます。

Task { await doWork() }

Task.detached: コンテキストを引き継ぎません。隔離の継承を明示的に切断する必要がある場合にのみ使用してください。

Task.immediate: 現在のアクター上で即座に開始します。レイテンシーが重視される処理に使用してください。

Task.immediate { await handleUserInput() }

async let: 同時実行する処理の数が固定されている場合に使用します。

async let a = fetchA()
async let b = fetchB()
let result = try await (a, b)

TaskGroup: 同時実行する処理の数が動的な場合に使用します。

try await withThrowingTaskGroup(of: Item.self) { group in
    for id in ids {
        group.addTask { try await fetch(id) }
    }
    for try await item in group { process(item) }
}

タスクのキャンセル

  • キャンセルは協調的に行われます。ループ内でTask.isCancelledを確認するか、try Task.checkCancellation()を呼び出してください。
  • SwiftUIでは.task修飾子を使用してください。ビューが消えたときのキャンセルを処理します。
  • クリーンアップにはwithTaskCancellationHandlerを使用してください。
  • Swift 6.4 / iOS 27+ betaでは、キャンセル後も完了させる必要がある短時間のクリーンアップまたはロールバックに限り、withTaskCancellationShieldを使用してください。シールド内ではTask.isCancelledはfalseになり、Task.checkCancellation()はスローしません。スコープの終了後は、再びキャンセルを検出できます。
  • 保存したタスクはdeinitまたはonDisappearでキャンセルしてください。

アクターの再入可能性

アクターは再入可能です。中断ポイントをまたぐと、状態が変化することがあります。

// WRONG: State may change during await
actor Counter {
    var count = 0
    func increment() async {
        let current = count
        await someWork()
        count = current + 1  // BUG: count may have changed
    }
}

// CORRECT: Mutate synchronously, no reentrancy risk
actor Counter {
    var count = 0
    func increment() { count += 1 }
}

AsyncSequenceとAsyncStream

コールバック/デリゲートAPIを橋渡しするには、AsyncStreamを使用します。

let stream = AsyncStream<Location> { continuation in
    let delegate = LocationDelegate { location in
        continuation.yield(location)
    }
    continuation.onTermination = { _ in delegate.stop() }
    delegate.start()
}

単一の値を返すコールバックには、withCheckedContinuation/withCheckedThrowingContinuationを使用します。必ず1回だけ再開してください。

@Observableと並行処理

  • ビューモデル用の@Observableクラスには@MainActorを指定してください。
  • @Observableインスタンスの所有には@Stateを使用してください(@StateObjectの代わりになります)。
  • @ObservableプロパティをAsyncSequenceとして非同期に監視するには、Observations { }(SE-0475)を使用してください。

同期プリミティブ

アクターが適切でない場合(同期アクセス、パフォーマンスが重要な処理経路、C/ObjC との橋渡しなど)は、低レベルの同期プリミティブを使用します。

  • アクターは、呼び出し元が一時停止できる非同期の共有状態に対するデフォルトの選択肢です。コンパイラーによって分離が強制され、構造化並行処理と統合されます。一方、外部からの呼び出しは非同期のアクター呼び出しとなり、await をまたぐ再入可能性への配慮が必要で、同期 C コールバックには適しません。UI に関連する共有状態には @MainActor などのグローバルアクターを使用してください。同期の代替手段として nonisolated(unsafe) を使用してはいけません。
  • Mutex<Value>(iOS 18 以降、Synchronization モジュール):新しいコードで推奨されるロックです。保護対象の状態をロック内に格納します。withLock { } パターンを使用します。
  • OSAllocatedUnfairLock(iOS 16 以降、os モジュール):より古い iOS バージョンを対象とする場合に使用します。デバッグ用の所有権アサーションをサポートしています。
  • Atomic<Value>(iOS 18 以降、Synchronization モジュール):独立したカウンターやフラグに使う、ロックフリーのアトミック型です。Atomic は Sendable であり、Sendable な保持型に格納できます。単独のメトリクスに限り .relaxed を使用し、ほかのデータと連携する場合は acquire/release の順序付け、またはロックを使用してください。

**重要なルール:**アクター内にロックを配置してはいけません(二重の同期になります)。また、await をまたいでロックを保持してはいけません(一時停止中もスレッドをブロックするため、協調プールの枯渇やデッドロックを引き起こす可能性があります)。API の詳細、コード例、ロックとアクターの選択ガイドについては、references/synchronization-primitives.md を参照してください。 Mutex.withLock と OSAllocatedUnfairLock.withLock は同期クロージャーを使用します。この API 形式によって、クリティカルセクションが中断されないようになっています。 Mutex と Atomic は if #available(iOS 18, *) で実行時に分岐させてください。#if swift(...) やプラットフォームのコンパイル時チェックは使用しないでください。NSLock については、誤った Sendable の前提だけを正し、適合の仕組みについては説明しないでください。 レガシーなロックのラッパーで @unchecked Sendable が本当に必要な場合は、次の不変条件を明記してください。可変状態はすべてプライベートであり、すべてのアクセスが単一のロックを使用し、可変参照が外部に漏れず、await をまたいでロックが保持されないこと。

よくある間違い

  1. メインアクターをブロックする。 @MainActor 上で重い計算を行うと、UI がフリーズします。 @concurrent 関数に移してください。
  2. 不要な @MainActor。 ネットワーク層、データ処理、モデルのコードに @MainActor は必要ありません。UI に触れるコードにのみ必要です。
  3. ステートレスなコードにアクターを使う。 可変状態がなければ、アクターは必要ありません。通常の構造体または関数を使用してください。
  4. 不変データにアクターを使う。 アクターではなく、Sendable 構造体を使用してください。
  5. 正当な理由なく Task.detached を使う。 優先度、タスクローカル値、キャンセルの伝播が失われます。
  6. タスクのキャンセルを忘れる。 Task 参照を保持してキャンセルするか、.task ビューモディファイアを使用してください。
  7. Task 内の循環参照。 長期間保持されるタスクで self をキャプチャするときは、[weak self] を使用してください。
  8. 非同期コンテキストでセマフォを使う。 DispatchSemaphore.wait() は協調型エグゼキューターのスレッドをブロックする可能性があり、シグナルを送る側がブロックされたエグゼキューターを必要とするとデッドロックが発生することがあります。非同期プリミティブまたは構造化並行性を使用してください。
  9. 分割された分離。 1 つの型で @MainActor と nonisolated のプロパティを混在させています。型全体を一貫して分離してください。
  10. 静的な分離の代わりに MainActor.run を使う。 await MainActor.run { } よりも @MainActor func を優先してください。
  11. 新しい非同期オーケストレーションに GCD をデフォルトで使う。 async/await、アクター、タスクグループを優先してください。API がキューを必要とする場合、カスタムエグゼキューターを使う場合、または範囲を限定したレガシー相互運用の場合は、ディスパッチキューを引き続き使用してください。測定済みの同期 CPU バウンド並列 for では、引き続き DispatchQueue.concurrentPerform を使用できます。タスクグループは非同期設計の代替手段であり、同期 API の置き換えとしてそのまま使えるものではありません。 ブリッジングと相互運用にあるキャプチャとメモリー安全性の監査に従ってください。 この診断をレビューする際は、毎回、その @Sendable 操作と 2 つの非 Sendable の安全でないバッファービューとの関係を明示してください。また、オプトアウトは、根拠が確認されたローカルのベースポインターバインディングに限定してください。並列処理を維持するレビューでは、ベンチマークと逐次処理の比較、逐次処理の結果、入れ子になった並列処理のチェックを必ず求めてください。

レビューチェックリスト

  • すべての可変共有状態がアクターによって分離されている
  • データ競合がない(分離をまたぐ保護されていないアクセスがない)
  • 不要になったタスクがキャンセルされる
  • @MainActor 上にブロッキング呼び出しがない
  • アクター内で手動ロックを使用していない
  • Sendable 適合が正しい(正当な理由のない @unchecked がない)
  • アクターの再入可能性に対処している(await をまたいで状態を仮定していない)
  • @preconcurrency インポートに削除計画が記載されている
  • 重い処理に @MainActor ではなく @concurrent を使用している
  • SwiftUI で手動の Task 管理ではなく .task モディファイアを使用している
  • 同期並列ループで、メモリーアクセスが競合しないこととポインターの有効期間に関する不変条件を証明している

参考資料

コメント