PC パソコン

Xcode「Main actor-isolated default value of ‘self.openURL’ cannot be used in a nonisolated initializer」の解決方法

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内のアクションから利用するのが推奨されます。

参考資料