@StateObjectはSwiftUIのproperty wrapperであり、Viewのライフサイクル全体にわたってObservableObjectインスタンスを作成し、所有します。Viewが初めて画面に表示されるとき、@StateObjectはオブジェクトを初期化し、Viewがメモリから削除されるまでそれを保持します。これにより、インターフェースの再構築時(テーマの変更や親Viewの更新など)にデータがリセットされないことが保証されます。Apple Developer Documentation(2025)によると、@StateObjectはSwiftUI階層内でObservableObjectの主要な情報源(source of truth)として使用されるべきであり、子Viewは@ObservedObjectまたは@EnvironmentObjectを介して既に作成されたオブジェクトを受け取ります。
主なポイント
@StateObjectはiOS 14で導入されたproperty wrapperで、ViewがObservableObjectプロトコルに準拠するクラスのインスタンスを作成し、所有することを可能にします。値型(struct)で動作する@Stateとは異なり、@StateObjectは参照型 — プロパティの変更をSwiftUIに通知できるクラス — 向けに設計されています。
Viewが@StateObject var viewModel: MyViewModelを使用すると、SwiftUIはViewが最初に表示されるときにMyViewModelのインスタンスを自動的に作成し、フレームワークの特別なストレージに保存します。Viewが更新されるたび(たとえば、親の状態が変わったとき)、SwiftUIはオブジェクトを再作成せず、Viewが階層から削除されるまで既存のインスタンスを使用します。
Apple WWDC Session 10137(2024)によると、@StateObjectはiOS 13でViewが再構築されたときに存在していたデータ損失問題を解決します。それまでは開発者は親ViewでObservableObjectを作成し、イニシャライザを介して渡す必要があり、コードの重複や誤ったオブジェクト再作成のリスクがありました。
import SwiftUI
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct CounterView: View {
@StateObject var viewModel = CounterViewModel()
var body: some View {
VStack {
Text("Count: \(viewModel.count)")
Button("Increment", action: viewModel.increment)
}
}
}
@StateObjectのメカニズムは、SwiftUIとCombineフレームワークの統合に基づいています。ObservableObjectがそのプロパティを@Published属性でマークすると、SwiftUIはObservableObjectプロトコルに組み込まれたパブリッシャーを介して変更を自動的に購読します。公開されたプロパティが変更されると、オブジェクトはobjectWillChangeパブリッシャーを通じてシグナルを送信し、このオブジェクトを監視するすべてのViewの再描画をトリガーします。
SwiftUIはObservableObjectインスタンスを特定のViewインスタンスに紐付けられた特別なストレージに保存します。このストレージは最初のレンダリング時に一度作成され、Viewが破棄されるまで存在します。これが@StateObjectが参照の安定性を保証する理由です — SwiftUIはViewのイニシャライザに依存せず、メモリを自動的に管理します。
objc.io — Thinking in SwiftUI(2025)によると、@StateObjectの内部実装は@Stateと同様のメカニズムを使用しますが、参照型用です。SwiftUIはオブジェクトの周りにボクシングラッパーを作成し、頻繁なView階層の再構築に最適化された独自のアロケーターを介してライフサイクルを管理します。
@StateObjectと@ObservedObjectの主な違いは、誰がオブジェクトを所有するかです。@StateObjectはオブジェクトを作成し保存します — それが所有者です。@ObservedObjectは、他の場所で作成されイニシャライザやプロパティを介して渡されたオブジェクトを監視するだけです。
| 特性 | @StateObject | @ObservedObject |
|---|---|---|
| 所有権 | オブジェクトを作成し所有する | 監視のみ |
| 初期化 | View内でinit/デフォルトを介して | 外部、パラメータを介して渡される |
| ライフサイクル | Viewのライフサイクルに紐付く | Viewによって制御されない |
| 再作成 | 更新時に再作成されない | 外部から置き換えられる可能性がある |
| iOSバージョン | iOS 14+ | iOS 13+ |
ルールはシンプルです。ViewがObservableObjectを作成する場合は@StateObjectを使用します。Viewが親から既に作成されたオブジェクトを受け取るだけの場合は@ObservedObjectを使用します。このルールに違反すると、データ損失(所有権に@ObservedObjectを使用した場合)または過剰なオブジェクト作成(監視に@StateObjectを使用した場合)のいずれかが発生します。
@StateObjectは、特定のデータセットの情報源となるViewで使用する必要があります。典型的なシナリオには、独自のview modelを持つ画面、ナビゲーションスタックのルート画面、および自身の状態を管理するモーダル表示が含まれます。
struct ProfileView: View {
@StateObject var viewModel = ProfileViewModel()
var body: some View {
NavigationStack {
Form {
TextField("Name", text: $viewModel.name)
TextField("Email", text: $viewModel.email)
Button("Save") {
viewModel.saveProfile()
}
}
.navigationTitle("Profile")
}
}
}
@StateObjectをパラメータ付きで初期化するには特別な構文が必要です。なぜならSwiftUIがオブジェクト作成を自身で管理するからです。イニシャライザに単にパラメータを渡すことはできません — エスケープクロージャまたは別のファクトリメソッドを使用する必要があります。
Swift by Sundell(2024)によると、最もクリーンな方法は、SwiftUIがオブジェクトを最初に作成するときに呼び出すファクトリメソッドまたはクロージャを使用することです。別のアプローチとしては、親ViewでObservableObjectを初期化し、標準のイニシャライザを使用して@StateObjectを介して渡す方法があります。
class UserViewModel: ObservableObject {
@Published var user: User
init(user: User) {
self.user = user
}
}
struct UserDetailView: View {
@StateObject var viewModel: UserViewModel
init(user: User) {
_viewModel = StateObject(wrappedValue: UserViewModel(user: user))
}
var body: some View {
Text(viewModel.user.name)
}
}
@StateObjectを使用するViewのイニシャライザは、property wrapper自体にアクセスするためにプロパティ名の前にアンダースコア(_viewModel)を使用する必要があることに注意してください。これはイニシャライザでproperty wrapperを扱うための標準的なSwiftパターンです。
最も一般的な間違いは、オブジェクトを所有すべきViewで@StateObjectの代わりに@ObservedObjectを使用することです。この場合、親が再構築されるたびにオブジェクトが再作成され、蓄積されたすべてのデータが失われます。この間違いは、NavigationStackやTabViewを使用する複雑な階層で特に厄介です。
これらの問題を避けるために、シンプルなルールに従ってください。情報源ごとに1つの@StateObject。データを複数の画面で共有する必要がある場合は、ルートViewで@StateObjectを一度作成し、@ObservedObjectまたは@EnvironmentObjectを介して子要素に渡します。
// ❌ Wrong: @ObservedObject for owning an object
struct BadView: View {
@ObservedObject var vm = ViewModel() // will be recreated on each update!
}
// ✅ Correct: @StateObject for owning
struct GoodView: View {
@StateObject var vm = ViewModel() // created once for View lifetime
}
よくある質問
@Stateは値型(struct、文字列、数値)で動作し、値を直接SwiftUIストレージに保存します。@StateObjectは参照型 — ObservableObjectに準拠するクラス — で動作します。@Stateは単純なローカル状態に適しており、@StateObjectはロジックと公開プロパティを持つ複雑なオブジェクトに適しています。
いいえ、@StateObjectはiOS 14以降でのみ利用可能です。iOS 13の場合は、@ObservedObjectを使用し、手動のライフサイクル管理で@Stateを介して親ViewにObservableObjectを作成してください。代替案として、参照セマンティクスを必要としないデータには、classの代わりにstructで@Stateを使用する方法があります。
子ViewはObservableObjectの独自のコピーを作成し、親とは完全に独立したものになります。一方の変更は他方に影響しません。これはほとんどの場合エラーです。親からオブジェクトを受け取るには@ObservedObjectを使用し、View内で新しいオブジェクトを作成する場合にのみ@StateObjectを使用してください。
オブジェクトは、それを作成したViewがSwiftUI階層から完全に削除されたときに破棄されます。NavigationStackの画面の場合、ナビゲーションスタックからポップされたときに発生します。モーダルウィンドウの場合は、閉じられたときです。TabViewの場合は、Viewがキャッシュされていない場合、タブを切り替えたときです。
アンダースコアを介してproperty wrapperにアクセスするカスタムinitを使用します。_viewModel = StateObject(wrappedValue: MyViewModel(param: value))。このパターンにより、Viewのライフタイム中の単一オブジェクト作成の保証を維持しながら、任意のパラメータをObservableObjectに渡すことができます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。