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
@MainActor extension StickerModel: 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隔離準拠はここでは使用できません
}
}
核となるパターン — グローバル変数と静的変数
グローバル/静的状態を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
}
@MainActor extension StickerModel: Exportable { // 暗黙的に@MainActor準拠
func export() {
photoProcessor.exportAsPNG()
}
}
このモードはオプトイン形式であり、アプリ、スクリプト、その他の実行可能ターゲットに推奨されます。
核となるパターン — バックグラウンド処理のための@concurrent
実際の並列処理が必要な場合は、@concurrentを使用して明示的にオフロードします:
重要: この例では「親しみやすい並行性」ビルド設定(SE-0466 MainActorデフォルト隔離、SE-0461 非隔離非送信デフォルト)が必要です。これらが有効な場合、
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から開始 — まずシングルスレッドコードを書き、後で最適化する
- CPU負荷の高い処理にのみ
@concurrentを使用 — 画像処理、圧縮、複雑な計算 - アプリターゲットで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アプリ)
- パフォーマンス最適化 — 特定の負荷の高い計算をバックグラウンドにオフロードする場合