Swift 6.2の並行処理を実践で使う:MainActorをデフォルトにして、意図的に抜け出す
Swift 6.0はデータ競合をコンパイルエラーにしました。そして1年間、誰もがその代償を払うことになったのです。strict-concurrencyチェッカーは、ありふれたUIコードをSendable違反の壁と、「main actor-isolated property can not be referenced from a nonisolated context」というエラーの山に変えてしまいました。診断そのものは正しかった(そのコードは実際に競合し得たのです)のですが、その量がシグナルを埋もれさせてしまい、多くのチームはSwift 5モードに留まるか、エラーが静かになるまで@MainActorを振りかけるかのどちらかを選びました。
Swift 6.2はルールではなくデフォルトを変えます。データ競合安全性の保証は同じで、変わったのはコンパイラの出発点です。新しいデフォルトを採用すれば壁の大部分は消えます。なぜなら、コンパイラはあなたのアプリですでに成り立っていた事実を前提とするようになったからです。つまり、ほとんどのコードはメインアクター上で動き、そこから抜け出すのは意図的に、名前のついた場所においてだけ、ということです。これは私が941ファミリーのアプリ全体で採用しているモデルであり1、より大きな進化の弧の一片でもあります。What’s New in Swift roundup for 2026は、その後に続いた6.3および6.4の提案を通してこの流れをたどっています。その仕組みと、有効にしてもなお噛みついてくる6つのエラーを紹介します。
TL;DR
- SE-0466により、モジュール全体のデフォルトを
@MainActorにできます。SWIFT_DEFAULT_ACTOR_ISOLATION = MainActorを設定すれば、すべてのビュー、モデル、ビューモデルにアノテーションを付ける必要はなくなります2。 - SE-0461により、nonisolatedな
async関数はデフォルトで呼び出し元のアクター上で実行されます(nonisolated(nonsending))。そのため、async コードを呼び出してもアクターホップが強制されることはなくなり、それに伴っていた境界をまたぐSendableエラーも起きなくなります3。 @concurrentは脱出口です。 関数に@concurrentを付けると、重いCPU処理(デコード、画像処理)を、意図的に、目に見える形でバックグラウンドスレッドへ押し出せます4。- Xcode 26は新規プロジェクトに対して両方のデフォルトを有効にします。総括的なスイッチは
SWIFT_APPROACHABLE_CONCURRENCY = YESです5。 - このモデルは負担を逆転させます。すべての行がメイン以外で動いても安全だと証明するのではなく、すべてをメイン上に置き、抜け出すわずかな場所だけを証明するのです。
- 切り替え後も6つの具体的なエラーが残ります。6つともパターンが見えれば1行で直せます。
逆転と、それが重要な理由
旧モデル(Swift 6.0)では、コードは隔離するまでnonisolatedでした。UI状態に触れるすべての型に@MainActorが必要で、隔離境界をまたぐすべてのasync呼び出しにSendable準拠が必要となり、コンパイラはそのギャップを一つひとつ指摘しました。コードの95パーセントがすでにメインスレッド上で動いているアプリでは、疑いようもなかった事実を記述するために、その95パーセントへアノテーションを付ける作業に時間を費やしていたのです。
新モデル(Swift 6.2)では、コードはそこから抜け出すまでメインアクター上にあります。SE-0466によってメインアクター隔離をモジュールのデフォルトとして宣言できるので、ビューもそのモデルもヘルパーも、アノテーションを1つも付けずにすべて@MainActorになります2。次にSE-0461が2つめの税を取り除きます。nonisolatedなasync関数はグローバルエグゼキューターへホップする代わりに、それを呼び出したアクター上で動くようになるので、awaitしても隔離境界をまたいで引きずられることはなく、スコープ内のすべてにSendableを要求されることもありません3。
このメンタルモデルは、UIアプリが実際にどう振る舞うかと一致します。デフォルトでメインスレッド、というのは妥協ではなく、状態がそのままビューであるアプリの真実なのです。並行処理は、すべての行で身構える環境的な前提ではなく、名前がつき封じ込められた、手を伸ばして使う例外になります。コンパイラの仕事は「これを並行に動かしても安全だと証明せよ」から「あなたがこれは並行に動くと言ったのだから、安全だと証明せよ」へと反転し、後者の問いはずっと少ない場所でしか問われなくなります。
Swift コアチームも同じことを述べています。WWDC 2026 Swift Group Labで、Hollyは、今日改めて並行処理モデルを設計するなら、nonisolated asyncな関数は初日から呼び出し元のコンテキストで動いていただろう、と語りました。彼女は、自分は長らくグローバル並行プールのモデルに固執していたが、実際のプロジェクトで起きた問題によって考えを改めさせられた、と要約して述べています。Dougは、旧来のデフォルトはあまりに多くの型をSendableへと押しやっていた、と付け加えました。(どちらの発言も、ローカルで文字起こしされたラボ録音からの言い換えです。Appleはこのセッションのキャプションを公開していません。)9
有効にする
2つのビルド設定です。どちらもXcode 26の新規プロジェクトではデフォルトであり、既存プロジェクトでも明示的に設定する価値があります5。
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor
SWIFT_APPROACHABLE_CONCURRENCY = YES
1つめはSE-0466で、モジュールのデフォルトをメインアクター隔離にします。2つめは総括的な設定で、SE-0461の「呼び出し元で動く」挙動を含む approachable-concurrency 機能群を有効にします。Swift パッケージでは、Xcode のビルド設定ではなく、対応する upcoming-feature フラグを使ってswiftSettingsで同じデフォルトを設定します6。
既存プロジェクトで両方を有効にすると、エラー件数は大きく減ります。チェッカーが指摘していたものの多くは、それまでメインスレッドだと前提にできなかったメインスレッドコードだったからです。残るのは、本物の境界ケースの短いリストです。それらは名前で知っておく価値があります。一つひとつが、あなたのコードが実際にメインアクターから抜け出す場所であり、その修正とは、それを正確に明言することだからです。
切り替え後も残る6つのエラー
これらはSWIFT_DEFAULT_ACTOR_ISOLATION = MainActorの下でもなお現れるstrict-concurrencyエラーで、941ファミリーのアプリをこのモデルへ移行した経験から拾い上げたものです1。どれも偽陽性ではなく本物の境界です。だからこそ修正は、抑制ではなく正確なアノテーションになります。
1. Sendable型上の純粋関数。 Sendableなenum上の純粋メソッド(URLビルダーやフォーマッター)は、モジュールのデフォルトの下でメインアクター隔離を継承し、nonisolatedなコンテキストから呼ばれるとエラーになります。「Call to main actor-isolated instance method in a synchronous nonisolated context.」 このメソッドはどの状態にも触れないので、メインアクターに隔離するのは誤りです。nonisolatedを付けましょう7。
nonisolated func searchURL(for query: String) -> URL? { ... }
2. デフォルト引数の中のシングルトンstatic。 static let shared = Foo()はデフォルトの下でメインアクター隔離されますが、デフォルト引数の値は呼び出し元のコンテキストで評価され、それはしばしばnonisolatedです。「Main actor-isolated static property ‘shared’ can not be referenced from a nonisolated context.」 このstaticをnonisolatedにします。その型がSendableなら(あるいは状態を自分で守っているために@unchecked Sendableなら)、unsafeな修飾子は要りません。
final class KeychainProxySecretStore: @unchecked Sendable {
nonisolated static let shared = KeychainProxySecretStore()
}
3. 不変のプリミティブ定数。 同じデフォルト引数の問題が、ただの定数にも当たります。nonisolatedなデフォルト引数から参照されるstatic let defaultIntervalです。修正は同じで、この定数は共有しても自明に安全です。
nonisolated static let defaultInterval: TimeInterval = 15 * 60
4. キャプチャしたselfを読むTask本体。 外側のクロージャが[weak self]をキャプチャし、その内側でTask { @MainActor in self?.foo() }がそのキャプチャしたオプショナルを読みます。「Reference to captured var ‘self’ in concurrently-executing code.」 このTaskは囲っているスコープのvarバインディングを並行に読んでおり、これが競合です。Taskの境界でselfをキャプチャし直し、Taskが不変のバインディングを所有するようにします。
NotificationCenter.default.addObserver(...) { [weak self] _ in
Task { @MainActor [weak self] in
self?.value = next
}
}
5. メインアクター状態を読むKVOコールバック。 webView.observe(\.canGoBack) { wv, _ in ... }というコールバックは@Sendableであり、したがってnonisolatedですが、WKWebView.canGoBackはメインアクター隔離されています。「Main actor-isolated property ‘canGoBack’ can not be referenced from a Sendable closure.」 KVOは値を変更したスレッド上で同期的に配信され、WKWebViewのナビゲーション状態はメインスレッド上でしか変化しないので、この読み取りは健全です。MainActor.assumeIsolatedでそれを表明しましょう。これはTaskホップを完全に取り除き、同期のままにします8。
let pushNav: @Sendable (WKWebView?) -> Void = { [weak self] webView in
MainActor.assumeIsolated {
guard let self else { return }
// safe to read webView?.canGoBack synchronously
}
}
assumeIsolatedはコンパイラへの問いではなく、約束です。ランタイムの不変条件が本当に成り立つ場所(ドキュメント化されたメインスレッドのコールバック)でのみ使ってください。約束を破れば、それは警告ではなくクラッシュになるからです。
6. メイン上にあるべきでない重い処理。 これはチェッカーが指摘しないもので、自分で捕まえることが最も重要です。メインアクターのデフォルトの下では、同期的でCPU負荷の高いメソッド(大きなペイロードのJSONデコード、画像のリサイズ)がメインアクター上で動き、UIをカクつかせます。デフォルトはあなたをメイン上に留めます。@concurrentは、意図的に抜け出すための手段です4。
@concurrent
func decodeLargePayload(_ data: Data) async throws -> Report {
try JSONDecoder().decode(Report.self, from: data)
}
@concurrentは関数をグローバルエグゼキューターへ降ろします。そして設計上、@MainActor、カスタムのグローバルアクター、nonisolated(nonsending)とは相互排他です。関数は、呼び出し元のいる場所で動くか、意図的にそこから離れて動くかのどちらかであり、決して曖昧ではありません4。新しいモデルが求める規律は、すべてこのパターンの中に宿っています。UIに触れるものはすべてメイン上に留め、@concurrentに手を伸ばすのは、計測上バックグラウンドスレッドを必要とする処理だけにしましょう。
デフォルトがあなたに合わないとき
デフォルトでメインアクター、という方針はアプリに合います。SwiftUIやUIKitのコードは圧倒的にメインスレッドであり、デフォルトが現実と一致します。次の2つのケースではあまり合わず、そうでないふりをするのは時間の無駄です。
- UIのないライブラリやフレームワークのターゲット。 ネットワーク層、パーサー、データエンジンには、デフォルトをメインアクターにする理由がなく、そうするとほぼすべてに
@concurrentかnonisolatedを強いることになります。それらのターゲットではSWIFT_DEFAULT_ACTOR_ISOLATIONを未設定のままにして、昔ながらのやり方で意図的に隔離しましょう。 - アクター中心の並行システム。 設計が本当に多くの処理を並列に動かすなら(数個のバックグラウンドタスクを抱えたアプリではなく、本物のパイプライン)、メインアクターのデフォルトはあなたと衝突します。明示的なアクターとnonisolatedなコードが欲しくなり、SE-0466のデフォルトは誤った出発点です。
とはいえアプリにとっては判断は簡単です。両方の設定を有効にし、エラー件数が崩れ落ちるに任せ、残ったわずかなものを、あなたのコードがメインスレッドから抜け出す場所そのものの地図として扱いましょう。その地図は持つ価値があります。旧モデルが与えてくれたのは1,000の警告と地図のなさでした。新モデルが与えてくれるのは、6つの正直な境界と、アプリの動き方にようやく一致したデフォルトです。
ノイズとシグナルについて最後に一言。SourceKitは、Xcodeがインデックスを再構築している間、エディタ内でファイルをまたぐインデックスエラー(「Cannot find type X in scope」「No such module」)を表示します。特にプロジェクトを再生成した直後によく起きます。これらはインデックスのアーティファクトであって、並行処理エラーではありません。xcodebuildがBUILD SUCCEEDEDと報告するなら、並行処理モデルは満たされており、エディタが追いついていないだけです1。インデックスの亡霊を追いかけるのは、すでに動いている移行作業で午後をまるごと無駄にする最速の方法です。
FAQ
Swift 6.2でメインアクターをデフォルトにする並行処理を有効にするには?
2つのビルド設定を行います。どちらも新規のXcode 26プロジェクトではデフォルトで有効です。SWIFT_DEFAULT_ACTOR_ISOLATION = MainActorとSWIFT_APPROACHABLE_CONCURRENCY = YESです5。Swift パッケージでは、Xcode のビルド設定の代わりに、swiftSettingsを通して同等の upcoming-feature フラグを設定します6。
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActorは実際に何を変えるのですか?
モジュールのコードをデフォルトでメインアクター隔離にします(SE-0466)。そのため、ビューもそのモデルもヘルパーも、アノテーションなしですべて@MainActorになります2。並行処理は、すべての行で身構えてアノテーションを付ける環境的な前提ではなく、手を伸ばして使う名前のついた例外になります。
@concurrentとは何で、いつ使うべきですか?
@concurrentはasync関数を、意図的に、呼び出し元のアクターから離してグローバルエグゼキューターへ降ろします4。メインアクターのデフォルトの下では、計測上バックグラウンドスレッドを必要とする処理(大きなペイロードのデコード、画像のリサイズ)に使います。@MainActorとは相互排他なので、関数は呼び出し元のいる場所で動くか、意図的にそこから離れて動くかのどちらかになります。
なぜ「Main actor-isolated … can not be referenced from a nonisolated context」が出るのですか?
メインアクターのデフォルトの下では、純粋関数、シングルトンstatic、定数がメインアクター隔離を継承し、デフォルト引数の評価のようなnonisolatedなコンテキストから呼ばれるとエラーになります。修正はそのメンバーへの正確なnonisolatedアノテーションであって、抑制ではありません。その値はどのアクター状態にも触れないので、共有しても安全です7。
ライブラリやフレームワークのターゲットに、メインアクターのデフォルトを使うべきですか?
いいえ。ネットワーク層、パーサー、データエンジンには、デフォルトをメインアクターにする理由がなく、そうするとほぼすべてに@concurrentかnonisolatedを強いることになります。UIを持たないターゲットではSWIFT_DEFAULT_ACTOR_ISOLATIONを未設定のままにして、昔ながらのやり方で意図的に隔離しましょう。
Swift 6.2では、nonisolated asyncな関数をawaitするとやはりアクターをホップしますか?
いいえ。SE-0461により、nonisolatedなasync関数はグローバルエグゼキューターへホップする代わりに、それを呼び出したアクター上で動きます。そのため、awaitしても隔離境界をまたいで引きずられることはなく、スコープ内のすべてにSendableを要求されることもありません3。
-
941ファミリーのiOSアプリ(Ki、Return、Get Bananas)全体にわたる著者のプロダクションコード。いずれも
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActorとSWIFT_APPROACHABLE_CONCURRENCY = YESを有効にして出荷されています。以下の6つのエラーパターンと修正は、Ki 1.0.0のstrict-concurrencyクリーンアップ作業の完全な一式でした。 ↩↩↩ -
Swift Evolution、SE-0466: Control default actor isolation inference。モジュールのデフォルトを
@MainActor隔離にできるようにし、UIやアプリのターゲットは、@concurrentまたは明示的なアクターによってオプトアウトしない限りメインアクター上で動きます。 ↩↩↩ -
Swift Evolution、SE-0461: Run nonisolated async functions on the caller’s actor by default。nonisolatedな
async関数はデフォルトでnonisolated(nonsending)になり、グローバルエグゼキューターへホップする代わりに呼び出し元のアクター上で動きます。これにより、そのホップに伴っていた境界をまたぐSendable要件がなくなります。 ↩↩↩ -
Swift Evolution、SE-0461は、関数をグローバルエグゼキューター(バックグラウンドスレッド)上で動かすことにオプトインするための
@concurrent属性を導入します。@concurrentとnonisolated(nonsending)は、nonisolated asyncな関数に対する2つの相反する隔離モードです。関数は、呼び出し元のいる場所で動くか、意図的にそこから離れて動くかのどちらかです。@concurrentは@MainActorやカスタムのグローバルアクターと組み合わせることはできません。 ↩↩↩↩ -
SWIFT_APPROACHABLE_CONCURRENCYは、approachable-concurrencyの upcoming features(SE-0461の挙動を含む)を有効にする総括的なXcodeビルド設定で、SWIFT_DEFAULT_ACTOR_ISOLATIONはモジュールのデフォルト隔離を選択します。新規のXcode 26プロジェクトは、メインアクターのデフォルトで両方を有効にします。Donny Wals著「Exploring concurrency changes in Swift 6.2」およびPaul Hudson著「What’s new in Swift 6.2」に記載されており、いずれも基となる提案SE-0461およびSE-0466と相互参照しています。 ↩↩↩ -
Swift、Swift Concurrency Migration Guide、「Enabling Complete Concurrency Checking」および言語モードの設定。Swift パッケージでは、デフォルト隔離とapproachable-concurrency機能は、Xcode のビルド設定ではなく
swiftSettingsの upcoming-feature フラグを通して設定します。 ↩↩ -
Swift、Migration Guide: global actor isolation and
nonisolated。@MainActor型のメソッドはメインアクター隔離を継承します。nonisolatedはメソッドをそこからオプトアウトさせ、これは隔離された状態に触れない純粋関数にとって正しい選択です。 ↩↩ -
Apple Developer、
MainActor.assumeIsolated(_:)。現在の実行がすでにメインアクター上であることを表明し、アクターホップなしでクロージャを同期的に実行します。不変条件が成り立たない場合、この表明はランタイムでトラップするため、呼び出し元がメインスレッド上にあると保証される場所でのみ有効です。 ↩ -
Apple、WWDC 2026 Swift Group Lab。Swift コアチームのHollyとDougに帰せられる発言は、ローカルで文字起こしされた録音からの言い換えです。Appleはこのセッションの公式な書き起こしやキャプションを公開していないため、文言は逐語的な引用ではなく、おおよそのものです。 ↩