LoginSignup
4
3

【SwiftUI】子ビューでは@StateObjectと@ObservedObjectのどちらを使うべきなのか

Last updated at Posted at 2024-01-01

こんにちは!むらおです!
SwiftUIで画面分割をした際、親ビューでは@StateObjectを使用するべきだと思いますが、子ビューではどうなのかが気になったので、今回はむらお調査兵団による調査結果をご紹介します

@.StateObjectと@.ObservedObjectの違いについて

細かい説明は他の方の記事を読んでいただきたいのですが、1番の違いはライフサイクルだと思います。
@.StateObject:Viewが表示されてから非表示になるまで
@.ObservedObject:親ビューのbodyが更新されるたび

参考

【SwiftUI】@StateObjectと@ObservedObjectの違いと使い分け

調査報告

コード

以下のようなコードで調査しました。
よくある構成だと思います。
ParentViewのbody内が大きくなったので、ChildViewにstructとして切り出した場面を想定しています。
View

// 親ビュー
struct  ParentView<ViewModel: ParentViewModel>: View {
    @StateObject var vm: ViewModel

    var body: some View {
        ChildView(vm: vm)
        Text(vm.text)
            .border(Color.black)
        TextField("placeholder", text: $vm.text)
    }
}

// 関係ないビュー
struct ChildView<ViewModel: ParentViewModel>: View {
// ここをいじって調査する
+   @StateObject var vm: ViewModel

    var body: some View {
        let _ = Self._printChanges()
        Text("子View")
    }
}

ViewModel

protocol ParentViewModel: ObservableObject {
    var text: String { get set }
}

class ParentViewModelImpl: ParentViewModel {
    @Published var text: String = ""
}

調査方法

ParentViewTextFieldに文字を入力することで、ChildViewの更新有無を調査しようと思います。
SwiftUI3.0で、Self._printChanges()というプライベートメソッドが追加されました。これを使用することで、Viewの更新トリガーを調べることができます。

_hoge changed

状態の変化によりViewが再レンダリングされた場合、トリガーが出力される

@.self

View自信が更新されたときに出力される

@.identity

ViewのIDが変わったときに出力される

結果

ChildView@StateObjectを使用した場合

CleanShot 2024-01-01 at 16.54.04.gif

TextFieldに文字を入力するたびに、

ChildView<ParentViewModelImpl>: @self, _vm changed.

と出力されています。
つまり、ChildViewView自信の更新が、vmの変更をトリガーに行われていることが分かります

ChildView@ObservedObjectを使用した場合

CleanShot 2024-01-01 at 16.53.49.gif

TextFieldに文字を入力するたびに、

ChildView<ParentViewModelImpl>: _vm changed.

と出力されています。
つまり、vmに変更があっただけで、何も更新されていないことが分かりました。

結論

子ビューで@StateObjectを使用した場合、親ビューのvmの状態が変わると、子ビュー本体が更新されるようです。
ここの理解が難しかったので、ChatGPTに聞きました。

ChatGPTの回答
子ビューで@StateObjectを使用した場合、子ビューは親ビューから渡されたViewModelに対する新しいバインディングを作成します。これはViewModelの新しいインスタンスを作成するわけではありません。それは親ビューが所有するViewModelの同じインスタンスを参照します。 しかし、子ビューがこのViewModelに対して@StateObjectを使用してバインディングすると、子ビューはこのViewModelの所有者であると誤解し、その状態の変更を監視するために子ビュー自体の再描画を引き起こす可能性があります。

一方、子ビューで@ObservedObjectを使用した場合、親ビューのvmの変更だけが子ビューに通知され、該当箇所(今回はなし)があれば差分更新されるようです。
親ビューでは、@StateObjectを使用し、子ビューでは、@ObservedObjectを使うべきなのかなと思いました。

参考文献

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3