Swift 6.2 と Xcode 26 で変わる並行処理と新たなビルド設定 nonisolated(nonsending) By Default について

# はじめに

Swift 6 では、安全な並行処理を推進するため、データ競合を防ぐ厳格なチェックが導入されました。
これにより安全性が向上した一方で、既存コードの多くでコンパイルエラーが発生する可能性が生じました。

この課題に対し、[SE-0461](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0461-async-function-isolation.md) が提案され、Swift 6.2 から `nonisolated(nonsending)` キーワードと、それをデフォルトで有効にするビルド設定 `nonisolated(nonsending) By Default` が Xcode 26 で導入されます。
これらの機能により、`async` 関数がデフォルトで呼び出し元のアクターを継承するようになります。

本記事では、

1. Swift 6 で厳格になった並行処理チェックとその影響
2. Swift 6.2 の `nonisolated(nonsending)` による緩和策
3. Xcode 26 のビルド設定 `nonisolated(nonsending) By Default` と併用を検討すべき `@concurrent`

について紹介します。

# Swift 6 での並行処理の厳格なチェック

Swift 6 ではデータ競合を防ぐため、並行処理の厳格なチェックが導入されました。
`Sendable` プロトコルへの準拠やアクター隔離が厳密に検証されます。

次のコードはその例です。

```swift
struct ContentView: View {
    let repository = Repository() // Repository は Sendable ではない

    var body: some View {
        EmptyView()
            .task {
                await repository.load() // ❌ データ競合の可能性
            }
    }
}

class Repository {
    func load() async { /* 処理をする */ }
}
```

このコードでは、`ContentView` が `@MainActor` に隔離されていますが、`Task` 内の処理は新しい並行コンテキストで実行されます。
`Sendable` に準拠していない `Repository` インスタンスが異なるコンテキスト間でアクセスされるため、コンパイラがデータ競合のリスクを検出してコンパイルエラーを発生させます。

これに対して、いくつかの解決方法が考えられます。

#### 1. 呼び出し先を `@MainActor` に隔離する

呼び出し先へのアクセスを呼び出し元のアクター (この場合は `@MainActor` ) に制限することで、データ競合を防ぎます。

```swift
@MainActor
class Repository { // クラス全体を `@MainActor` に隔離する
    func load() async { /* */ }
}

// または

class Repository {
    @MainActor // 特定の関数のみを @MainActor に隔離する
    func load() async { /* */ }
}
```

#### 2. `Repository` を `Sendable` に準拠させる

`Repository` を `Sendable` に準拠させることで、インスタンスが並行コンテキスト間で安全に共有できることを保証します。

```swift
final class Repository: Sendable { // Sendableに準拠させる
    // プロパティも全てSendableである必要がある
    func load() async { /* */ }
}

// または

actor Repository { // actorとして定義することでSendableに準拠し、内部状態へのアクセスが排他的になる
    func load() async { /* */ }
}
```

#### 3. キャプチャリストを利用して値を分離する

クロージャのキャプチャリストを利用して、`repository` の参照をクロージャ内のローカルコピーとして扱います。
これにより、異なる並行コンテキスト間で同じインスタンスが共有されるのを避け、そのタスク内でのみ有効な独立した参照として扱うことで、データ競合のリスクを回避できます。

この方法は、一時的に非 `Sendable` なオブジェクトを並行コンテキストで使用する場合に有効ですが、オブジェクトのライフサイクルや共有状態の管理に注意が必要です。

```swift
var body: some View {
    EmptyView()
        .task { [repository] in // repositoryをキャプチャし、クロージャ内で独立して使用する
            await repository.load()
        }
}
```

これらの方法は、コードの設計やパフォーマンス要件に応じて使い分ける必要があります。
特に、重たい処理をメインスレッドで実行してしまうことは、UIのフリーズなどを引き起こすため注意が必要です。

# Swift 6.2 で登場する `nonisolated(nonsending)` とは

Swift 6.1 以前では、呼び出し元のアクターで実行させたい場合に、明示的に `@MainActor` などを付与する必要がありました。

Swift 6.2 から導入される `nonisolated(nonsending)` キーワードは、この挙動をより簡潔に記述できるようにします。
`nonisolated(nonsending)` が付与された `async` 関数は、呼び出し元のアクターを継承して実行されます。

```swift
struct ContentView: View {
    let repository = Repository()

    var body: some View {
        EmptyView()
            .task {
                await repository.load() // エラーが発生しない
            }
    }
}

class Repository { // Sendable でなくても OK
    // ↓ 追加
    nonisolated(nonsending) func load() async {
        // 呼び出し元アクターで動く (この場合は MainActor)
    }
}
```

上記の例では、`ContentView` の `.task` 内から `repository.load()` を呼び出しています。
`.task` 内のクロージャは、暗黙的に `@MainActor` のコンテキストで実行されるため、`nonisolated(nonsending)` が付与された `load()` メソッドも `@MainActor` 上で実行されます。
もし `load()` が別のアクターから呼ばれた場合は、そのアクターのコンテキストで実行されます。

この機能により、呼び出し元のアクターのコンテキストを維持したまま `async` 関数を実行したい場合に、アクター属性の指定が不要になります。

# Xcode 26 のビルド設定 `nonisolated(nonsending) By Default` について

Xcode 26 から登場したビルド設定 `nonisolated(nonsending) By Default` は、上述の `nonisolated(nonsending)` を暗黙的に追加するオプションです。

これにより、呼び出し元のアクターを継承させたい場合に毎回 `nonisolated(nonsending)` を記述する手間が省けます。

ただし副作用として、この設定が有効な場合、連鎖するすべての呼び出し先が `nonisolated(nonsending)` となります。
そのため、UI層から呼び出すとほぼすべての処理が `@MainActor` で実行され、重い処理がメインアクターで実行される可能性があります。

これに対し、`@concurrent` を使うことで解決が可能です。

# `@concurrent` で明示的に並列化する

`nonisolated(nonsending) By Default` が有効になっている場合でも、メインアクターで実行したくない時間のかかる重い処理は、明示的に並列実行させる必要があります。
そのためのアノテーションが `@concurrent` です。

```swift
struct ContentView: View {
    let repository = Repository()

    var body: some View {
        EmptyView()
            .task {
                await repository.load()
            }
    }
}

@MainActor
class Repository {
    @concurrent // async 関数に @concurrent をつける
    func load() async {
        // メインスレッド以外で実行される
    }
}
```

上記の例では `Repository` に `@MainActor` を付与していますが、`Sendable` に準拠させても問題ありません。

`@concurrent` を適切に利用することで、`nonisolated(nonsending) By Default` の利点を享受しつつ、アプリケーションのパフォーマンスを維持することができます。

# まとめ

Swift 6.2 から導入される `nonisolated(nonsending)` と `@concurrent`、そして Xcode 26 の新しいビルド設定 `nonisolated(nonsending) By Default` により、並行処理のコードがより書きやすくなります。

具体的には、これらの機能により以下が可能になります。

- `async` 関数はデフォルトで呼び出し元のアクターで実行
- 重い処理には `@concurrent` を使用してバックグラウンド実行を明示的に指定

これらの改善により、Swift 6 の厳格な並行処理チェックを保ちながら、より実用的なコードを記述できるようになります。
どの処理をどのアクターで実行すべきか、または新しい並行タスクで実行すべきかを見極めながら、これらの設定とアノテーションを効果的に活用していきたいです。

# 参考

- [Code Along：Swiftの並行処理によるアプリの強化](https://developer.apple.com/jp/videos/play/wwdc2025/270/)
- [Run nonisolated async functions on the caller's actor by default](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0461-async-function-isolation.md)