👋

[SwiftUI]StateObjectの挙動から戦略を考える

2021/08/06に公開

@StateObjectとは

iOS14から使用でき、ObservableObjectを扱えます。ObservedObjectと似ていますが、挙動が違います。

ObservedObject

親Viewが再描画されたら、その度に更新されます。実装方法によっては初期化されてしまいます。

struct ObservedChildView: View {
    
    @ObservedObject var counter = Counter()
        //親Viewが更新されたら、このCounterは初期化される

    var body: some View {
        HStack {
            Text("[ObservedObject]Increment")
            Button.init(action: { counter.increment() }, label: {
                Image(systemName: "plus")
            })
            Text("\(counter.count)")
        }
    }
}

StateObject

親Viewが再描画されても、値は保持されます。

struct StateChildView: View {
    
    @StateObject var counter = Counter()
    //親Viewが更新されても、このCounterは保持されたままです
    
    var body: some View {
        HStack {
            Text("[StateObject]Increment")
            Button.init(action: { counter.increment() }, label: {
                Image(systemName: "plus")
            })
            Text("\(counter.count)")
        }
    }
}

ObservedObject/StateObjectは画面に1つであるべきか

@ObservedObject@StateObjectを設定できるのは、ObservableObject protocolを継承しているクラスのみです。MVVMアーキテクチャを考える場合、ObservableObjectViewModelに当たり、ロジックが中心となっています。
一般的なMVVMを考えるのであれば、1つの画面にはViewModelが1つ、つまりObservableObjectもしくはStateObjectは1つということになります。

iOS14以前は、子ViewにObservedObjectを設定していても初期化されてしまう場合があるので、1つの画面にViewModel/ObservableObjectは1つであることがほとんどでした。しかし、StateObjectが出たことによりViewModel/StateObjectが複数あっても挙動には問題がなくなりました。

もう一度MVVMアーキテクチャを考えてみると、ViewModel/StateObjectが複数ある場合、1つの画面に対するロジックが分散されてしまい、メンテナンス性が下がる可能性があります。ですので理想としては、

1つのViewに対してViewModel/StateObject/ObservedObjectは1つ

が良いと考えられます。

もし、ViewModelで取得した値を子Viewで使いたい場合はBindingを利用することを推奨します。

StateObjectが画面に2つ以上ある場合を考える

StateObjectViewの中に複数ある場合を考えます。

  • 親View
    • 子View.1(StateObject.1)
    • 子View.2(StateObject.2)

このような構成の場合、前述のViewViewModelが1対1であることからは外れてしまいますが、Viewの部分更新による描画コストの節約が可能と考えられます。

具体的なレイアウトを考えると

  • マイページ
    • アカウント情報
    • 最近投稿したコンテンツ

など、1つの画面で2つ程度の大きなカテゴリの情報を表示する場合が考えられます。上記の場合、アカウント情報はあまり変更ありませんが、最近投稿したコンテンツは頻繁に更新があると考えられます。
全体の更新(再描画)は避けることで、描画パフォーマンスが上がると考えられます。

とはいえ、1つのViewViewModel/StateObjectが4つかそれ以上もあるとロジックも分散され、メンテナンス性が下がります。複数あるとしても、2〜3個までにしたほうが良いでしょう。

結論

StateObjectの挙動、MVVMアーキテクチャから考えると、

  • ViewModel/StateObjectは基本1つの画面に対して1つ
  • 子ViewにはBindingで情報を渡す
  • 再描画コスト・パフォーマンスが気になる場合は1つの画面に対してViewModel/StateObjectは2〜3個程度

参考

https://developer.apple.com/documentation/swiftui/stateobject
https://tokizuoh.dev/posts/td152tqrb7iflicp/

Discussion