能登 要

札幌在住のiOSアプリ開発者。SwiftUI により分割されたデバイス間を縦横にやりとりできる考え方に転換しています。

iOSアプリ開発者。2009年のiPhoneアプリ開発開始時期から活動。開発言語のアップデートの中でSwiftUIおよび周辺技術に着目中。

StateObjectCatalog(@StateObject の機能確認カタログプロジェクト for Xcode12 beta3以上)

SwiftUI 2nd major release で追加された@StateObject の機能を確認するためのXcodeカタログワークスペース。比較対象としてSwiftUI 1st major releaseで用意された@ObservedObject を用意し、オブジェクトの寿命の違いを確認する。

notoroid/StateObjectCatalog

機能を確認するための4パターンx2種の計8プロジェクトが含まれている。@StateObject の機能を確認するためのコードの違いを読み取れるかでSwiftUIの習得度を図ることもできるかもしれない。

FooManagerクラス、InnerObjectクラスは全プロジェクトの共有のコードで、それぞれApp構築層のModel、View内部での保持するオブジェクトに相当する。ContentViewはXcodeのMultiplatformテンプレートウィザードで生成されるトップレベルのViewプロジェクトごとにカスタマイズして使用している。

全てのプロジェクトでアプリ構築層に@StateObject 指定したFooManagerクラスのインスタンスを使用しているがこちらはWWSC2020のサンプルで示されている一般的な用法なので今回は説明を省略する。

InnerObjectクラスの初期化と終了処理の呼び出し回数に@StateObject@ObservedObject で違いがあるかを確認してほしい、ログとしてInnerObjectのinit/deinitを出力している。

class InnerObject: ObservableObject {
    init() {
        print("InnerObject: init call.")
    }
    
    deinit {
        print("InnerObject: deinit call.")
    }
}

1) 各プロジェクト紹介

1-1) UseStateObject/UseObservedObject

ContentView の内部でObservableObjectを派生したInnerObject のインスタンスを保持している。FooModel.event を監視し変更があった場合は表示が切り替わる。

1-2) 〜WithParameter

ContentView の内部でObservableObjectを派生したInnerObject のインスタンスを保持している点は変わらないが、ContentViewの変数eventで表示が切り替わるように変更を加えている。

1-3) 〜DependencyInjection

ObservableObjectを派生したInnerObject を外部から渡された場合の挙動を確認できる。

1-4) 〜ToggleView

FooModel.event の監視元をContentView を切り替えた場合の挙動を確認できる。

2) カタログプロジェクトを確認する

Xcode12 beta3以上を持っている場合はぜひカタログプロジェクトをチェックアウトしてiOS シミュレーター上で動作を確認してみてほしい。

確認方法はiOSシミュレーターで各プロジェクトを選択し実行、画面中央のToggleボタンを複数回タップ、ログにInnerObjectのinit/deinit の呼び出し方の違いを確認する。

3) 答え合わせ

Xcode12 beta3での結果は以下となる。

Header @StateObject @ObservedObject
Use〜 1度だけ生成 複数生成
Use〜WithParameter 1度だけ生成 複数生成
Use〜DependencyInjection 1度だけ生成 複数生成
Use〜ToggleView 複数生成 複数生成

@ObservedObject がSwiftUI内部のレンダリング都度インスタンスが生成されているのに対し、@StateObject はView内部の変更が発生してもインスタンス生成は1度だけとなっている。

例外としてはルートに近いレベルでViewの消滅が行われると複数生成されるようになる。

4)まとめ

@StateObject と@ObservedObject の機能の違い確認した事で@StateObject の特性、SwiftUI がレンダリングした結果生成されるオブジェクトのライフサイクルと一致している事が確認できた。

@StateObject の挙動はSwiftUI の概念よりかは必要に迫られて追加した感があるが、@StateObject を使う事でアプリ上の処理をこれまでより切り分けしやすくなる効果がある。

副作用として、@StateObject 導入はデータの分散というオブジェクトベースのアプリで起きがちな自称を再発する恐れもある。

とはいえ、サブシシテムとして切り分けられるのが適切な箇所は存在するのでそれらに@StateObject を導入するのが筋ではないか。

具体的な用途としては、アプリ構築層のModelに指定する例はWWDC2020のデモで示されている。ほかDocumentGroupで生成されるViewに渡すパラメーターとして@StateObject を指定するとドキュメントのデータソースをViewに供給できるし、モーダル表示にさらにモーダルを表示するといった特殊な状態を保持するなどの用途が考えられる。