Swift 6.2 アプローチ可能な並行処理
Swift 6.2 の並行処理モデルを採用するためのパターンです。このモデルでは、コードはデフォルトでシングルスレッドで実行され、並行処理は明示的に導入されます。一般的なデータ競合エラーを排除しつつ、パフォーマンスを犠牲にしません。
アクティブ化するタイミング
- Swift 5.x または 6.0/6.1 プロジェクトを Swift 6.2 に移行する場合
- データ競合安全性に関するコンパイラエラーを解決する場合
- MainActor ベースのアプリケーションアーキテクチャを設計する場合
- CPU 負荷の高い処理をバックグラウンドスレッドにオフロードする場合
- MainActor 分離型でプロトコル準拠を実装する場合
- Xcode 26 でアプローチ可能な並行処理のビルド設定を有効にする場合
中核的な問題: 暗黙的なバックグラウンドオフロード
Swift 6.1 以前では、async 関数が暗黙的にバックグラウンドスレッドにオフロードされ、一見安全なコードでもデータ競合エラーが発生する可能性がありました:
// Swift 6.1: エラー
@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 }
// エラー: 'self.photoProcessor' を送信するとデータ競合のリスクが発生します
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
Swift 6.2 で修正: async 関数はデフォルトで呼び出し元のアクターに留まります。
// Swift 6.2: OK — async は MainActor に留まり、データ競合なし
@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 }
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
中核パターン — 分離準拠
MainActor 型は、非分離プロトコルに安全に準拠できるようになりました:
protocol Exportable {
func export()
}
// Swift 6.1: エラー — メインアクター分離コードにクロス
// Swift 6.2: 分離準拠により OK
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}
コンパイラは、準拠がメインアクター上でのみ使用されることを保証します:
// OK — ImageExporter も @MainActor
@MainActor
struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // 安全: 同じアクター分離
}
}
// エラー — 非分離コンテキストは MainActor 準拠を使用できない
nonisolated struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // エラー: メインアクター分離準拠はここでは使用できません
}
}
中核パターン — グローバル変数と静的変数
グローバル/静的状態を MainActor で保護:
// Swift 6.1: エラー — non-Sendable 型は共有可変状態を持つ可能性があります
final class StickerLibrary {
static let shared: StickerLibrary = .init() // エラー
}
// 修正: @MainActor で注釈
@MainActor
final class StickerLibrary {
static let shared: StickerLibrary = .init() // OK
}
MainActor デフォルト推論モード
Swift 6.2 では、MainActor がデフォルトで推論されるモードが導入されました — 手動での注釈は不要:
// MainActor デフォルト推論を有効にした場合:
final class StickerLibrary {
static let shared: StickerLibrary = .init() // 暗黙的に @MainActor
}
final class StickerModel {
let photoProcessor: PhotoProcessor
var selection: [PhotosPickerItem] // 暗黙的に @MainActor
}
extension StickerModel: Exportable { // 暗黙的に @MainActor 準拠
func export() {
photoProcessor.exportAsPNG()
}
}
このモードはオプトイン方式で、アプリ、スクリプト、その他の実行可能ターゲットに推奨されます。
中核パターン — バックグラウンド処理のための @concurrent
実際の並列処理が必要な場合は、@concurrent で明示的にオフロードします:
重要: この例にはアプローチ可能な並行処理のビルド設定が必要です — SE-0466 (MainActor デフォルト分離) と SE-0461 (NonisolatedNonsendingByDefault)。これらを有効にすると、
extractStickerは呼び出し元のアクターに留まり、可変状態へのアクセスが安全になります。これらの設定がない場合、このコードにはデータ競合があります — コンパイラがフラグを立てます。
nonisolated final class PhotoProcessor {
private 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 { /* ... */ }
}
// 呼び出し側は await が必要
let processor = PhotoProcessor()
processedPhotos[item.id] = await processor.extractSticker(data: data, with: item.id)
@concurrent を使用するには:
- 包含する型を
nonisolatedとしてマーク - 関数に
@concurrentを追加 - まだ非同期でない場合は
asyncを追加 - 呼び出し箇所に
awaitを追加
主要な設計上の決定
| 決定 | 根拠 |
|---|---|
| デフォルトでシングルスレッド | 最も自然なコードはデータ競合がなく、並行処理はオプトイン |
| async は呼び出し元のアクターに留まる | データ競合エラーの原因となっていた暗黙的なオフロードを排除 |
| 分離準拠 | MainActor 型が安全でない回避策なしでプロトコルに準拠可能 |
@concurrent による明示的なオプトイン | バックグラウンド実行は意図的なパフォーマンスの選択であり、偶発的ではない |
| MainActor デフォルト推論 | アプリターゲットの @MainActor 注釈のボイラープレートを削減 |
| オプトインによる採用 | 非破壊的な移行パス — 機能を段階的に有効化 |
移行手順
- Xcode で有効化: ビルド設定の Swift Compiler > Concurrency セクション
- SPM で有効化: パッケージマニフェストで
SwiftSettingsAPI を使用 - 移行ツールを使用: swift.org/migration による自動コード変更
- MainActor のデフォルトから開始: アプリターゲットの推論モードを有効化
- 必要な場所に
@concurrentを追加: 最初にプロファイリングし、ホットパスをオフロード - 徹底的にテスト: データ競合の問題がコンパイル時エラーになる
ベストプラクティス
- MainActor から開始 — まずシングルスレッドコードを書き、後で最適化
@concurrentは CPU 負荷の高い処理にのみ使用 — 画像処理、圧縮、複雑な計算- アプリターゲットでは MainActor 推論モードを有効化 — ほとんどがシングルスレッドの場合
- オフロード前にプロファイリング — Instruments を使用して実際のボトルネックを特定
- グローバルを MainActor で保護 — グローバル/静的の可変状態にはアクター分離が必要
nonisolatedの回避策や@Sendableラッパーの代わりに分離準拠を使用- 段階的に移行 — ビルド設定で機能を一度に一つずつ有効化
避けるべきアンチパターン
- すべての async 関数に
@concurrentを適用する (ほとんどはバックグラウンド実行を必要としない) - 分離を理解せずに
nonisolatedを使用してコンパイラエラーを抑制する - アクターが同等の安全性を提供するのに、古い
DispatchQueueパターンを維持する - 並行処理関連の Foundation Models コードで
model.availabilityチェックをスキップする - コンパイラと戦う — データ競合を報告する場合、コードには実際の並行処理の問題がある
- すべての async コードがバックグラウンドで実行されると仮定する (Swift 6.2 のデフォルト: 呼び出し元のアクターに留まる)
使用するタイミング
- すべての新しい Swift 6.2+ プロジェクト (アプローチ可能な並行処理が推奨デフォルト)
- 既存の Swift 5.x または 6.0/6.1 並行処理からのアプリ移行
- Xcode 26 採用中のデータ競合安全性コンパイラエラーの解決
- MainActor 中心のアプリケーションアーキテクチャの構築 (ほとんどの UI アプリ)
- パフォーマンス最適化 — 特定の高負荷計算をバックグラウンドにオフロード