SwiftUIの状態管理戦略の下調べをしてまとめてみた
SwiftUI で開発を行う際、最初の壁になるであろう状態管理の戦略について簡単に下調べしてみました。
状態管理戦略というのは "一般的な不具合"、"非同期処理による状態の不整合"、"データ量増大に伴うパフォーマンスの著しい低下" を抑制するために、SwiftUI における ObservedObject や StateObject 等をどのように使うべきか、チームメンバーに理解してもらうための文書のことを指しています。
おことわり
私は大規模な SwiftUI アプリ開発を行ったことはありません。過去に制作したことがあるのは小規模な MacOS アプリのみです。また、2022年現時点での SwiftUI の最新機能もキャッチアップできていません。知識としてはかなり浅いのでその点ご了承ください。
また、この記事にて何らかの回答を示すわけでもありませんのでその点もご了承ください。
チーム統制のために整理すべきだと思う事項
SwiftUI でチーム開発を行う場合、状態管理の統制はある程度必要だと思われます。その際に必要となるのが状態管理の戦略です。この戦略を立てる上で必要になりそうな事項を簡単にまとめてみました。この記事を書いている2022年6月時点では、これら全てをひとまとめに整理した文書は確認できていません。そのため、各開発チームで作り上げるしかないだろうというのが私の認識です。
基礎知識面
状態管理に関わるプロパティラッパー(State, Binding, ObservedObject, StateObject, EnvironmentObject)の挙動を分かりやすく説明した文書
Combine, Swift Concurrency による非同期処理の行い方を分かりやすく説明した文書
チーム内における副作用という用語の定義
ObservedObject が想定していないタイミングで初期化されてしまう設計バグのケーススタディ
戦略面
モデル(≒ビューモデル)の分離(分解)方針(原則としてはモデルごとの責務を最小化する)
どこで @State を使うべきか
どこで @Binding を使うべきか
ObservableObject への依存関係
どのようなモデルを、どこで @ObservedObject として配置するか
どのようなモデルを、どこで @StateObject として配置するか
どのようなモデルを、どこで @EnvironmentObject として配置するか
シングルトンパターンをどこで採用するか、あるいは全面的に禁止するか
非同期処理を所属させるのは、どのようなモデルか
副作用を引き起こす処理を所属させるのは、どのようなモデルか
どのように非同期処理を行うか
可視化
開発規模が大きくなっても俯瞰できるようにするためにモデルの階層構造や影響範囲をグラフィカルに可視化するためのツール(現状、該当するツールは確認できていません)
戦略を立てる際に参考になりそうな文書
記事/SwiftUIの状態管理の基礎
私が必要になりそうだと思うあらゆる戦略を網羅的に記した文書は今のところ見当たりませんが(たぶんこれだけで本が一冊書けると思いますが)、部分的に戦略を説明している文書を調査しました。
こちらの記事は、状態管理の戦略に踏み込んで整理されている記事としてよくまとめられています。特に各種プロパティラッパーの使い分けについて、図を交えて説明されています。
動画/Data Essentials in Swift UI Apple 公式
先ほどの記事の情報ソースは以下の公式動画です。トランスクリプトがありますので文章でも読むことができます。
個人的には、冒頭で触れられている Source of Truth の理解が重要だと感じています。また 29:05 から先のセクションは、まさにどのプロパティラッパーを使うのか、どこに Source of Truth を置くか、の一般的なガイドラインを示しています。
プロジェクト/CodeEdit
GitHub にて SwiftUI を使用しており、スターの多いプロジェクトとして CodeEdit というプロダクトがありました。こちらはまだ開発中ですが、多くのコントリビュータが参加されています。
CodeEdit の Wiki には、コードスタイルガイドはあるものの、状態管理戦略についてまとめられたページはありませんでした。もしかすると Discord 上には議論があるかもしれませんが、現時点ではまだ確認していません。
特に気になる EnvironmentObject、シングルトンをどのようにしているのかソースコードの中身を読んでみました。
EnvironmentObject については乱用していません。唯一ワークスペースオブジェクトをトップに近いレベルでインジェクトしているのみです。(例)
反面シングルトンに関してはかなり利用しています。例えばアプリ設定を表す AppPreferencesModel がシングルトンとなっています。他にもテーマモデル、エクステンションマネージャー、フィードバックなどのモデルがシングルトンになっています。
コードを見ていただけると、SwiftUI を使ったアプリとしては規模が大きいため、読むだけでも参考になる部分が多いです。MacOS アプリのソースコードですので、全てが iOS アプリ開発に応用できるとは限りませんが、参考になる部分は多いと思います。
記事/【SwiftUI】なぜ、MVVMをやめて、The Composable Architecture(TCA)を採用するのか?
所謂 MVVM と呼ばれる設計パターンを SwiftUI に導入する是非について考察されている記事です。
Apple のフォーラムにも同様の議論が立てられています。
これらの記事やスレッドに違和感はなく、SwiftUI において MVVM は不要という考えは同意です。実は「MVVM は不要」という議論が出ること自体が驚きでした。SwiftUI は React のようなデザインのフレームワークなので、ビューのイベントと1対1に対応するようなモデルを作る必要は薄く、純粋にビジネスロジックのことを考えたモデルを作ればよいと考えています。
MVVM における ViewModel の主な責務であるデータバインディングを SwiftUI が担ってくれているので、「モデルの責任を最小化する」という原則を意識していれば自然に美しい設計になると個人的には思っています。
まとめ
React から SwiftUI に辿ってきた身からすると、SwiftUI は本当に自然に使える素晴らしいフレームワークだと思います。現在も UIKit の機能に頼るということがたびたびあるのだろうと思いますが、個人開発や小規模の開発などリスクの小さなプロジェクトであれば導入する価値はあると思います。
反面、従来の純粋な UIKit の世界から飛び込んだ人からすると、いろんなパラダイムシフトを起こす必要があり、慣れるまでが大変だろうと思います。おそらくそういった人たちの方が世間的には多数派だと思うので、将来的に上手いベストプラクティスが SwiftUI を使うエンジニアによって整備されれば幸いです。