PC パソコン

Xcode「variable ‘self.x’ used before being initialized」の解決方法【SwiftUI @State・Xcode 27】

Xcode 27で既存のSwiftUIビューをビルドすると、@Stateをinit内で設定するコードに「variable 'self.x' used before being initialized」が出ることがあります。これはSwiftUIのStateがproperty wrapperからSwiftマクロへ変わり、通常のstored propertyと同様に初期化規則へ参加するようになったことによるソース互換性の問題です。AppleのTN3211に沿って、安全な修正方法を整理します。

表示されるエラー

error: variable 'self.counter' used before being initialized

コードの形によっては、次の関連診断になることもあります。

error: 'self' used in property access '_counter' before 'super.init' call

再現例

import SwiftUI

struct ContentView: View {
    var name: String
    @State private var counter: Int

    init(name: String) {
        self.counter = 42
        self.name = name
    }

    var body: some View {
        Text("\(name): \(counter)")
    }
}

Xcode 27では、counterへの代入によってマクロが生成したbacking storageへアクセスします。しかし、その時点ではnameがまだ初期化されていないため、完全に初期化される前のself使用として診断されます。

原因

Xcode 27のSwiftUIでは、@Stateがproperty wrapperではなくSwiftマクロになりました。これにより、特に参照型の状態をビューの再生成ごとに作り直さず、遅延初期化できるようになります。一方、@Stateもビューの初期化順序に明示的に参加するため、以前は通っていた曖昧な初期化パターンがエラーになります。

ポイント: これは状態値の型が壊れたエラーではありません。init内の代入順序、または宣言時の既定値とinit内代入の重複が原因です。

発生条件と対象バージョン

  • Xcode 27でSwiftUIコードをビルドしている。
  • ビューに@Stateと、それ以外のstored propertyがある。
  • すべての非@Stateプロパティを初期化する前に、init内で@Stateへ代入している。
  • または、@Stateにインライン既定値があるのに、initでも別の値を代入している。

AppleはTN3211を2026年6月8日に公開し、Xcode 27での@Stateと@ContentBuilderのソース互換性を説明しています。遅延初期化のランタイム動作は、@Observableが導入されたiOS 17、macOS 14および対応リリースまでback deploymentされますが、このビルド診断への対応はXcode 27でコンパイルするソース側の修正です。

推奨する解決手順

1. 非@Stateのstored propertyを先に初期化する

import SwiftUI

struct ContentView: View {
    var name: String
    @State private var counter: Int

    init(name: String) {
        self.name = name
        self.counter = 42
    }

    var body: some View {
        Text("\(name): \(counter)")
    }
}

Appleの推奨どおり、すべての非@Stateプロパティを初期化してから@Stateへ代入します。プロジェクト内に同じ形のinitializerが複数ある場合も、各initializerで順序をそろえてください。

2. initから初期値を渡すなら、インライン既定値を外す

次のコードは避けます。

struct CounterView: View {
    @State private var counter = 0

    init(initialCount: Int) {
        self.counter = initialCount
    }

    var body: some View {
        Text("\(counter)")
    }
}

次のように、初期値の供給元をinitializerへ一本化します。

struct CounterView: View {
    @State private var counter: Int

    init(initialCount: Int) {
        self.counter = initialCount
    }

    var body: some View {
        Text("\(counter)")
    }
}

宣言時の既定値が残っていると、その値が優先され、init内の代入値が実行時に破棄される可能性があります。エラーだけを消すのではなく、状態の初期値が実際にどこから来るのかを一つに決めることが重要です。

代替策

  • 固定値でよい場合は、@State private var counter = 0のように宣言時だけで初期化し、init内では代入しません。
  • 親ビューから継続的に値を受け取るだけなら、ローカル状態へ複製せず、通常のlet/varや@Bindingが適切か再検討します。
  • 参照型の状態をビューの所有対象にする場合は、Xcode 27の遅延初期化を活かし、宣言時初期化かinitializer初期化のどちらか一方へ統一します。

注意: _counter = State(initialValue: ...)へ機械的に置き換える方法は、Xcode 27で@Stateがマクロ化された後の第一選択ではありません。まずAppleが示す「非@Stateを先に初期化」「インライン既定値とinit代入を重複させない」を適用してください。

修正後の確認方法

  1. Product > Clean Build Folderを実行し、再ビルドします。
  2. 対象ビューを生成するすべてのinitializerで、同じ診断が消えたことを確認します。
  3. init引数を変えてプレビューまたは実機を起動し、@Stateの初期値が期待どおり表示されることを確認します。
  4. ビューの親状態を更新して再評価させ、状態オブジェクトや値が意図せず再作成・上書きされないことを確認します。
  5. 複数構成でビルドする場合は、DebugだけでなくReleaseも確認します。

注意点

  • @Stateはビューの内部状態です。外部値との同期を目的に、親から渡された値を毎回コピーする設計には向きません。
  • 診断に表示されるプロパティ名はcounter、pageなど実コードに応じて変わります。self.xはTN3211の一般化された表記です。
  • Xcodeの不具合としてダウングレードだけで回避するより、Appleが示す初期化規則に沿ってソースを更新する方が、Xcode 27以降との互換性を保てます。

まとめ

variable 'self.x' used before being initializedは、Xcode 27で@Stateがマクロ化され、初期化順序が厳密に扱われるようになったことで発生します。非@Stateのstored propertyを先に初期化し、initializerから状態値を渡す場合はインライン既定値を削除してください。修正後は、ビルド成功だけでなく、実行時の初期値と再評価時の状態保持も確認します。

参照した公式情報