@StateObject: 概要、@ObservedObjectとの違い、例

著者: IT Sectr 公開日: 2026-06-19 読了時間: 7 分

@StateObjectは、SwiftUIでObservableObjectインスタンスをビュー内で直接作成および所有するためのProperty Wrapperです。SwiftUIは、オブジェクトがビューのライフサイクルごとに一度だけ初期化され、再レンダリング時に再作成されないことを保証します。Apple Developer Documentation (2025)によると、@StateObjectはデータソースを作成するルートビューに推奨されます。@StateObjectは、SwiftUI階層内でObservableObjectを所有するための正しい選択です。

重要なポイント

  • @StateObject — ビューでObservableObjectを作成および所有するためのProperty Wrapper
  • 単一インスタンス — オブジェクトは一度作成され、再レンダリング時に再作成されない
  • 信頼できる情報源 — @StateObjectは階層全体のデータ安定性を保証する
  • @ObservedObjectとの違い — @ObservedObjectはオブジェクトを所有せず、失う可能性がある
  • ルートビュー — @StateObjectはオブジェクトを作成するビューで使用される

SwiftUIの@StateObjectとは?

@StateObjectは、SwiftUI 2.0(iOS 14)で導入されたProperty Wrapperで、@ObservedObjectと@Stateの機能を組み合わせたものです。@ObservedObjectと同様に、ObservableObjectの変更を購読します。@Stateと同様に、データがビュー構造の繰り返しの初期化を生き残ることを保証します。@StateObjectはビューが最初に画面に表示されたときにオブジェクトを一度作成し、SwiftUIのヒープに保存します。

@StateObjectの登場前は、開発者はビューで作成されたものも含め、すべてのObservableObjectに@ObservedObjectを使用していました。これにより、親ビューが更新されるたびに頻繁なデータ損失が発生し、ビュー構造が再作成されて@ObservedObjectインスタンスも一緒に失われていました。@StateObjectは安定性の保証を追加することでこの問題を解決しました。

基本ルール:@StateObjectはデフォルトのイニシャライザ(let model = ViewModel())でオブジェクトを作成するビューで使用します。このオブジェクトを受け取る子ビューは@ObservedObjectを使用します。この分離により、階層全体で単一の信頼できる情報源が保証されます。

@StateObjectのライフサイクル

SwiftUIは、@Stateと同様のストレージマネージャを介して@StateObjectのライフサイクルを管理します。ビューが最初に表示されると、SwiftUIはオブジェクトにメモリを割り当て、永続領域に保存します。その後の再レンダリング(body呼び出し)では、オブジェクトは再作成されず、既存のインスタンスが使用されます。オブジェクトはビューが階層にある限り存続します。

ビューが階層から削除されると、SwiftUIは@StateObjectを破棄し、deinitを呼び出します。ビューが階層に再び追加されると、新しいインスタンスが作成されます。設計時にこれを考慮することが重要です:ビューが削除された間もデータを保持する必要がある場合は、サービスレイヤー(シングルトンやDI)または@AppStorageを使用してください。

swift
class TimerViewModel: ObservableObject {
    @Published var seconds: Int = 0
    private var timer: Timer?

    func start() {
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            self.seconds += 1
        }
    }

    deinit {
        timer?.invalidate()
    }
}

struct TimerView: View {
    @StateObject var viewModel = TimerViewModel()

    var body: some View {
        Text("\(viewModel.seconds)s")
            .onAppear { viewModel.start() }
    }
}

この例では、TimerViewModelが@StateObjectを介して作成され、TimerViewが画面にある限り存続します。タイマーはonAppearで開始され、deinitで停止します。@ObservedObjectを使用した場合、TimerViewがレンダリングされるたびにseconds = 0の新しいTimerViewModelが作成され、タイマーは正しく動作しません。@StateObjectはviewModelが一意で安定していることを保証します。

@StateObject vs @ObservedObject: 比較

@StateObjectと@ObservedObjectの選択は、誰がオブジェクトを所有するかに依存します。ビューがオブジェクトを作成する場合は@StateObject。ビューが既成のオブジェクトを受け取る場合は@ObservedObject。このルールは非常に重要なので、Xcodeはイニシャライザを介してオブジェクトを受け取る子ビューで@StateObjectを使用すると警告を表示します。

状況推奨Wrapper
ビューがViewModel()でモデルを作成@StateObject
ビューが親からモデルを受け取る@ObservedObject
モデルが1つのビューで使用される@StateObject
モデルがEnvironment経由で渡される@EnvironmentObject
プレビュー用にモデルが必要@ObservedObject + mock

実際のプロジェクトでは、@StateObjectはルートビューで、@ObservedObjectはすべての子ビューで使用されることがよくあります。アプリケーションの成長に伴い、階層を簡略化するために一部の@StateObjectインスタンスを@EnvironmentObjectに置き換えることができます。ただし、独自のロジックを持つモジュール式画面には、@StateObjectが最適な選択肢であり続けます。

@StateObjectの使用パターン

最初のパターン—@StateObjectを使ったMVVM。ObservableObjectとしてのViewModelが@StateObjectを介してビューで作成されます。ViewModelは@Publishedプロパティとビジネスロジックを含みます。ビューは変更を購読してインターフェースを更新します。このアプローチはテスト可能な分離を提供します:ViewModelはインスタンスを直接作成することでUIなしでテストできます。

2番目のパターン—依存関係を持つ@StateObject。ViewModelがサービスを必要とする場合は、パラメータを使った初期化を使用します。例:@StateObject var viewModel = UserViewModel(api: APIClient.shared)。ただし注意してください:パラメータはbodyのレンダリングごとに計算されますが、オブジェクトは一度だけ作成されます。SwiftUIは@StateObjectのその後の初期化を無視します。

3番目のパターン—ネストされた@StateObject。SwiftUIでは1つのビューに複数の@StateObjectを持つことができますが、それはめったに正当化されません。通常、1つの@StateObjectがビューのデータセット全体を処理します。ロジックが複雑になりすぎた場合は、1つの@StateObject内で@ObservedObjectサービスの構成に分割してください。

swift
struct AppView: View {
    @StateObject var router = NavigationRouter()
    @StateObject var auth = AuthViewModel()

    var body: some View {
        ContentView()
            .environmentObject(router)
            .environmentObject(auth)
    }
}

この例では、AppViewが2つの@StateObjectを作成します:ナビゲーション管理用のNavigationRouterと認証用のAuthViewModel。両方のオブジェクトはenvironmentObjectを介してEnvironmentに注入されます。任意の子ビューは、イニシャライザの連鎖を経由せずに@EnvironmentObjectを介してそれらにアクセスできます。

@StateObjectとパラメータを使った初期化

@StateObjectは任意のパラメータでの初期化をサポートしていますが、重要な注意点があります:イニシャライザは一度だけ呼び出されます。その後のbodyの再レンダリングでは、パラメータの新しい値は無視されます。つまり、@State var id: Int = 5を@StateObject var vm = ViewModel(id: id)に渡した場合、idが変更されてもViewModelは新しい値を受け取りません。

この問題を解決するには、同期にonReceiveまたはonAppearを使用します。Combineを介してViewModel内でパラメータ変更を購読するか、ビューレベルで.onChange(of:)メソッドを介してパラメータを渡します。別の方法として、オブジェクトが外部の変更に動的に応答する必要がある場合は、@StateObjectの代わりに@ObservedObjectを使用します。

swift
struct DetailView: View {
    let itemId: Int
    @StateObject var viewModel = DetailViewModel()

    var body: some View {
        Text(viewModel.title)
            .onAppear { viewModel.load(id: itemId) }
    }
}

正しいアプローチ:DetailViewはitemIdをletプロパティとして受け取り(構造体のイニシャライザを介して渡される)、@StateObjectはパラメータなしでDetailViewModelを作成します。onAppearで、渡されたIDのデータをロードするためにload(id:)メソッドが呼び出されます。これにより、ViewModelが@StateObjectメカニズムによって作成されつつ、現在のIDでビューが表示されるたびにデータがロードされることが保証されます。

@StateObjectのよくある間違い

主な間違いは、親からオブジェクトを受け取る子ビューで@StateObjectを使用することです。ParentViewが@StateObject modelを作成し、ChildViewが@StateObject var model: ModelType(デフォルトパラメータ付き)を宣言すると、ChildViewは独自の独立したインスタンスを作成します。親と子のオブジェクトは接続されず、一方の変更が他方に反映されません。

2番目の間違いは、ListやForEachで@StateObjectを使用することです。リストの各要素が独自の@StateObjectを作成し、複数の独立したインスタンスが生成されます。リストの場合は、@ObservedObjectを介してすべての要素に単一のObservableObjectを渡すか、List内で@Stateを持つIdentifiable構造体を使用するのが正しい方法です。

3番目の問題は、deinitクリーンアップの欠如です。@StateObjectはビューのライフサイクル全体にわたって存続します。オブジェクトがタイマー、Combineサブスクリプション、ネットワークリクエストを作成する場合、deinitはそれらをキャンセルする必要があります。そうしないと、メモリリークや画面クローズ後のバックグラウンド処理の継続が避けられません。CombineのCancellableストアを常に使用するか、deinitでタイマーを無効にしてください。

よくある質問

@StateObjectはSwiftUIでいつ導入されましたか?

@StateObjectはWWDC 2020でSwiftUI 2.0に追加され、iOS 14、macOS 11、watchOS 7、tvOS 14とともにリリースされました。それ以前は、@ObservedObjectがObservableObjectを扱う唯一の方法であり、データ損失のバグが頻繁に発生していました。

@StateObjectはオプショナルにできますか?

いいえ、@StateObjectはOptional型をサポートしていません。オブジェクトは宣言時に初期化する必要があります。オプショナルなオブジェクトが必要な場合は、オプショナル型で@ObservedObjectまたは@EnvironmentObjectを使用してください。

@StateObjectが一度だけ作成されることを確認するには?

ObservableObjectのイニシャライザとdeinitにprint(#function)を追加します。再レンダリング時にinitが呼び出されなければ、@StateObjectは正しく動作しています。毎回initが呼び出される場合は、@ObservedObjectを@StateObjectに置き換えてください。

@StateObjectはUIHostingControllerを介してUIKitで使用できますか?

はい、@StateObjectはUIHostingControllerを介してUIKitに埋め込まれたSwiftUIビューでも機能します。オブジェクトのライフサイクルはUIViewControllerではなくSwiftUIビューに結びついています。SwiftUIビューが置き換えられると、@StateObjectは破棄されます。

大きなViewModelを持つ1つの@StateObjectと、小さなものを複数持つのでは、どちらが良いですか?

責任が分離された複数の小さな@StateObjectsが良いです。これにより、テスト容易性、再利用性、パフォーマンスが向上します—オブジェクトが変更されると、ビュー全体ではなく、購読しているインターフェースの部分だけが再描画されます。

まとめ

  • @StateObject — ビューでObservableObjectを作成および所有するためのProperty Wrapper
  • 単一インスタンス — その後のbody再レンダリングでオブジェクトは再作成されない
  • 信頼できる情報源 — ルートビューの@StateObjectが階層のデータ安定性を保証する
  • 選択ルール — @StateObjectは作成用、@ObservedObjectは既成オブジェクト受信用
  • 初期化 — @StateObjectのパラメータは一度だけ計算され、更新は追跡されない
  • Deinit — ObservableObjectのdeinitでのタイマーとサブスクリプションの必須クリーンアップ
  • iOS 14+ — @StateObjectはiOS 14、macOS 11、watchOS 7、tvOS 14以降で利用可能

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください