swift-concurrency について
swift-concurrencyは、スレッドセーフなSwiftコードの作成、レビュー、近代化を支援する開発者向けスキルです。async/await、分離、タスク管理に加え、コールバック、キュー、Combineからの移行を扱います。
主な機能
- 01async Swiftコードの作成ガイド
- 02並行処理の監査チェックリスト
- 03分離とSendableの実践
- 04構造化された並列タスクのパターン
- 05SwiftUIの並行処理ガイド
- 06旧式の並行処理コードの改修
活用シーン
- 01新しい非同期APIの設計
- 02データ競合のコードレビュー
- 03コールバックからasync/awaitへ移行
- 04キュー方式のコードをactorへ改修
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ jamesrochabrun/ skills --skill swift-concurrencyインストール後、Claude Code で/swift-concurrencyを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/common-mistakes.mdfundamentals.mdglossary.mdisolation.mdsendable.md詳細
Swift Concurrency
概要
このスキルでは、最新の並行処理パターンを使って、スレッドセーフな Swift コードを記述する方法を説明します。主なワークフローとして、新しい非同期コードの作成、既存コードの問題点の監査、従来のパターンから Swift 6+ へのリファクタリングの3つを扱います。
基本原則:分離はデフォルトで継承されます。Approachable Concurrency では、コードは MainActor 上で実行を開始し、プログラム全体に自動的に伝播します。必要に応じて、明示的に適用を解除してください。
ワークフローの判断ツリー
What are you doing?
│
├─► BUILDING new async code
│ └─► See "Building Workflow" below
│
├─► AUDITING existing code
│ └─► See "Auditing Checklist" below
│
└─► REFACTORING legacy code
└─► See "Refactoring Workflow" below
作成ワークフロー
新しい非同期コードを記述する際は、次の判断プロセスに従ってください。
ステップ 1:分離の要件を判断する
Does this type manage UI state or interact with UI?
│
├─► YES → Mark with @MainActor
│
└─► NO → Does it have mutable state shared across contexts?
│
├─► YES → Consider: Can it live on MainActor anyway?
│ │
│ ├─► YES → Use @MainActor (simpler)
│ │
│ └─► NO → Use a custom actor (requires justification)
│
└─► NO → Leave non-isolated (default with Approachable Concurrency)
ステップ 2:非同期関数を設計する
// PREFER: Inherit caller's isolation (works everywhere)
func fetchData(isolation: isolated (any Actor)? = #isolation) async throws -> Data {
// Runs on whatever actor the caller is on
}
// USE WHEN: CPU-intensive work that must run in background
@concurrent
func processLargeFile() async -> Result { }
// AVOID: Non-isolated async without explicit choice
func ambiguousAsync() async { } // Where does this run?
ステップ 3:並列処理を扱う
// For known number of independent operations
async let avatar = fetchImage("avatar.jpg")
async let banner = fetchImage("banner.jpg")
let (a, b) = await (avatar, banner)
// For dynamic number of operations
try await withThrowingTaskGroup(of: Void.self) { group in
for id in userIDs {
group.addTask { try await fetchUser(id) }
}
try await group.waitForAll()
}
ステップ 4:SwiftUI との統合
struct ProfileView: View {
@State private var avatar: Image?
var body: some View {
avatar
.task { avatar = await downloadAvatar() } // Auto-cancels on disappear
.task(id: userID) { /* Reloads when userID changes */ }
}
}
// For user actions
Button("Save") {
Task { await saveProfile() } // Inherits MainActor isolation
}
監査チェックリスト
Swift の並行処理コードをレビューする際は、次の問題がないか確認してください。
重大な問題(必ず修正)
- 協調プールのブロック:非同期コンテキスト内で
DispatchSemaphore.wait()、DispatchGroup.wait()などのブロッキング呼び出しがないか確認します - データ競合:適切に処理されないまま、Sendable ではない型が分離境界を越えていないか確認します
- Sendable ではない型における非分離の非同期処理:これらは非分離コンテキストからしか機能しません
よくある問題(修正を推奨)
- Actor の過剰な使用:正当な理由のないカスタム Actor(リファレンスの「Actor Justification Test」を参照)
- 不要な
MainActor.run:通常は、代わりに関数に@MainActorを付けるべきです - 非同期ならバックグラウンドで実行されるという誤解:非同期関数内の同期的な CPU 処理は、引き続き実行をブロックします
- 構造化並行処理で対応できるのに非構造化 Task を使用:
async letやTaskGroupではなくTask { }を使用していないか確認します - キャンセル処理の不足:時間のかかる処理では
Task.isCancelledを確認してください
SwiftUI 固有
- View が MainActor で分離されていない:SwiftUI の View には
@MainActorを付けるか、@Observableを使用してください - 切り離された Task からの @State へのアクセス:MainActor に戻る必要があります
Sendable への準拠
- @unchecked Sendable の過剰な使用:使用はまれにとどめ、正当な理由が必要です
- すべての型を Sendable にする:すべての型が境界を越える必要があるわけではありません
- Sendable ではないクロージャのエスケープ:クロージャがキャプチャする値を確認します
リファクタリングのワークフロー
コールバックから async/await へ
// BEFORE: Callback-based
func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, error in
if let error { completion(.failure(error)); return }
// ...
}.resume()
}
// AFTER: async/await with continuation
func fetchUser(id: Int) async throws -> User {
try await withCheckedThrowingContinuation { continuation in
fetchUser(id: id) { result in
continuation.resume(with: result)
}
}
}
DispatchQueue から Actor へ
// BEFORE: Queue-based protection
class BankAccount {
private let queue = DispatchQueue(label: "account")
private var _balance: Double = 0
var balance: Double {
queue.sync { _balance }
}
func deposit(_ amount: Double) {
queue.async { self._balance += amount }
}
}
// AFTER: Actor (if truly needs own isolation)
actor BankAccount {
var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
}
// BETTER: MainActor class (if doesn't need concurrent access)
@MainActor
class BankAccount {
var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
}
Combine から AsyncSequence へ
// BEFORE: Combine publisher
cancellable = NotificationCenter.default
.publisher(for: .userDidLogin)
.sink { notification in /* ... */ }
// AFTER: AsyncSequence
for await _ in NotificationCenter.default.notifications(named: .userDidLogin) {
// Handle notification
}
クイックリファレンス
| キーワード | 用途 |
|---|---|
async | 関数が中断される可能性があることを示します |
await | 中断ポイント |
Task { } | 非同期処理を開始し、分離を継承します |
Task.detached { } | 非同期処理を開始し、分離を継承しません |
@MainActor | メインスレッドで実行します |
actor | 分離された可変状態を持つ型 |
nonisolated | Actor の分離を明示的に適用しないようにします |
nonisolated(nonsending) | 呼び出し元の分離を継承します |
@concurrent | 常にバックグラウンドで実行します(Swift 6.2+) |
Sendable | 分離境界を安全に越えられることを示します |
sending | Sendable ではない値を一方向に転送します |
async let | 並列処理を開始します |
TaskGroup | 動的な並列処理を行います |
Approachable Concurrency の設定(Swift 6.2+)
新しい Xcode 26+ プロジェクトでは、次の設定がデフォルトで有効になっています。
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor
SWIFT_APPROACHABLE_CONCURRENCY = YES
効果:
- 明示的に別の指定をしない限り、すべて MainActor 上で実行されます
nonisolated async関数はバックグラウンドに切り替わらず、呼び出し元の Actor 上で実行されます- Sendable に関するエラーが大幅に減少します
リソース
詳細な技術リファレンスについては、以下を参照してください。
references/fundamentals.md- async/await、Task、構造化並行処理references/isolation.md- Actor、MainActor、分離ドメイン、継承references/sendable.md- Sendable プロトコル、Sendable ではないパターン、分離されたパラメーターreferences/common-mistakes.md- 避けるべき事項の詳細な例references/glossary.md- 用語の完全なリファレンス
リファレンスの検索パターン:
- 分離:
grep -i "isolation\|actor\|mainactor\|nonisolated" - Sendable:
grep -i "sendable\|sending\|boundary" - Task:
grep -i "task\|taskgroup\|async let\|structured"
コメント
Markdown 対応