3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

はじめに

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

この課題に対し、SE-0461 が提案され、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 プロトコルへの準拠やアクター隔離が厳密に検証されます。

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

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 ) に制限することで、データ競合を防ぎます。

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

// または

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

2. RepositorySendable に準拠させる

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

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

// または

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

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

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

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

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 関数は、呼び出し元のアクターを継承して実行されます。

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 です。

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

参考

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?