SwiftUIのカスタムViewで@Environment(\.openURL)を使い、初期化子をnonisolatedにすると「Main actor-isolated default value of ‘self.openURL’ cannot be used in a nonisolated initializer」と表示されることがあります。AppleのSwiftUI Frameworks Engineerによれば、アプリ側のカスタムViewでは通常、初期化子をnonisolatedにする必要はありません。
発生するコードの例
struct HelpView: View {
@Environment(\.openURL) private var openURL
nonisolated init() {
}
var body: some View {
Button("Open Help") {
openURL(URL(string: "https://example.com/help")!)
}
}
}
openURLはSwiftUIのEnvironmentから供給され、Main Actorへ隔離されています。一方、nonisolated initはMain Actor外から呼ばれることを許すため、初期化時のプロパティラッパー設定と隔離条件が矛盾します。
原因
- Swift 6でSwiftUIの
ViewがMain Actor隔離として扱われる @Environmentの値がSwiftUIによってMain Actor上で解決される- カスタム初期化子へ明示的に
nonisolatedを付けた - 依存性注入コンテナなど、非隔離コンテキストからView自体を生成しようとしている
Apple EngineerはWWDC26 SwiftUI Q&Aで、カスタムViewは親Viewの隔離コンテキストから生成され、Viewプロトコル自体が@MainActorであるため、通常はカスタムView.initをnonisolatedにしないよう案内しています。SwiftUI内部のViewにnonisolated initがあるのは、古いコンパイラや多様なConcurrency設定との互換性を保つフレームワーク側の事情です。
推奨する解決手順
1. nonisolatedを削除する
最も直接的な解決策は、カスタムViewの初期化子を通常のMain Actor隔離へ戻すことです。
struct HelpView: View {
@Environment(\.openURL) private var openURL
init() {
}
var body: some View {
Button("Open Help") {
openURL(URL(string: "https://example.com/help")!)
}
}
}
明示的な初期化処理が不要なら、init自体を削除して合成初期化子に任せます。
2. openURLはbodyまたはMain Actorメソッドから使う
Environment値を初期化子で取り出したり保存したりせず、Viewのアクションで参照します。
struct HelpView: View {
@Environment(\.openURL) private var openURL
var body: some View {
Button("Open Help") {
guard let url = URL(string: "https://example.com/help") else {
return
}
openURL(url)
}
}
}
URLを外部から受け取る場合は、Environmentのアクションではなく、URLそのものを初期化子の引数にします。
struct HelpView: View {
let helpURL: URL
@Environment(\.openURL) private var openURL
var body: some View {
Button("Open Help") {
openURL(helpURL)
}
}
}
3. Viewを作るファクトリをMain Actorへ揃える
依存性注入コンテナやルーターが非隔離でViewを生成している場合は、Viewファクトリ側を@MainActorにします。
@MainActor
func makeHelpView() -> some View {
HelpView()
}
クラス全体がUI構築を担当するなら、型全体へ付ける方法もあります。
@MainActor
final class ViewFactory {
func makeHelpView() -> HelpView {
HelpView()
}
}
4. 依存性注入はViewではなくデータへ行う
Apple Engineerは、通常の依存性注入ではViewそのものよりデータフローを注入する設計を検討するよう説明しています。サービス、モデル、設定値、クロージャーなどをViewへ渡し、Viewは親Viewのbodyで生成します。
struct HelpView: View {
let title: String
let helpURL: URL
@Environment(\.openURL) private var openURL
var body: some View {
Button(title) {
openURL(helpURL)
}
}
}
@Stateでは初期化できるのに@Environmentでは失敗する理由
@StateはViewが所有する一時的な状態で、SwiftUIが初期値を受け取る専用の仕組みを持ちます。@Environmentは親View階層から実行時に注入される値であり、カスタム初期化子が自由に所有・設定する値ではありません。同じプロパティラッパーでも役割が異なるため、@Stateで使えた初期化パターンを@Environmentへそのまま適用できません。
代替策
- URLを通常の
letプロパティとして注入し、openURLはView内で取得する - UIファクトリや登録処理を
@MainActorへ移す - Viewの型消去が必要な境界では、設計を確認したうえで
AnyViewを利用する - 非UI層にはViewではなく、画面を表すデータやルート情報を返させる
避けるべき回避策
MainActor.assumeIsolatedを根拠なく使うnonisolated(unsafe)で検査を無効化する- Environment値をグローバル変数やSingletonへコピーする
DispatchQueue.main.asyncだけでコンパイル時の隔離設計を隠す
MainActor.assumeIsolatedは、実行コンテキストがMain Actorであることを外部条件で確実に証明できる高度な相互運用向けです。通常のSwiftUI View生成を直す第一選択ではありません。
確認方法
- カスタムViewの
initからnonisolatedを外してビルドできる - Viewを作成するファクトリやルーターが
@MainActorになっている openURLをbody内のButtonなどから正常に呼び出せる- Swift 6 Language ModeとComplete Strict Concurrencyでも警告が出ない
注意点
このエラーは特定のXcodeバージョン固有の不具合としてではなく、SwiftUIとSwift 6のActor隔離ルールに沿った診断としてAppleが説明しています。そのため、Xcodeの更新やDerivedData削除だけでは根本解決になりません。カスタムViewをMain Actor上で生成する設計へ修正してください。
まとめ
カスタムSwiftUI Viewでは、フレームワーク内部のViewをまねてnonisolated initを付ける必要は通常ありません。nonisolatedを削除し、ViewおよびViewファクトリをMain Actorへ揃え、@Environment(\.openURL)はbody内のアクションから利用するのが推奨されます。
