SwiftUIに適したアプリケーション設計を思考する

この記事は「eureka Advent Calendar 2019」16日目の記事です。

15日目はPairs JP Web Teamの新(@ooDEMi)による「Building Design System for Pairs」でした。


エウレカのiOSエンジニアのMuukiiです🤠
Pairsの日本版と台湾・韓国に展開している海外版のiOSアプリの開発を担当しております。

今回はSwiftUIとそのためのアプリケーション設計についてお話をします。


2019年はWWDCでSwiftUIが発表されて大きな話題となっています。
もちろんエウレカの中でもSwiftUIについて研究したり話し合ったりしています。

SwiftUIの登場により、いままでのiOSアプリ開発が変わることは確かだと思います。

その中でも特に私が関心を持っている部分として、

UIKitによるUIドリブンなプログラミングからSwiftUIによるデータドリブンなプログラミングになる

というところです。
この変化がアプリケーションの状態管理をより強くできる可能性があると考えています。

そこで、SwiftUIを使ったときの設計について考えてみました。
結論までは到達していませんが、これまで考えてきたことを考察としてまとめます。

複雑化する状態の組み合わせを上手く管理するためにアーキテクチャを選択する

デバイスの状態とアプリが提供するサービスにおける状態を組み合わせると想定すべきパターンは膨大な数になります。

考慮すべきパターンにひとつフラグが入るだけでパターンは単純計算で倍に、指数関数的に増えていきます。

WWDC 2019のSessionでもここについて触れていて、
不具合はパターンの複雑度が人間の能力を超えたときに発生する可能性が表れてくる。と説明されています。

不具合はシステムの複雑度が人間の能力を超えたときに発生する

この問題に対応していくために、様々なツールや設計手法が多くの開発者によって作られています。

  • Linter
  • Compiler
  • Application Architecture
  • Design Pattern
  • Code Generator
  • Unit Testing
  • UI Testing

SwiftUIもこの問題に対応するためのひとつのツールといえます。

では具体的にSwiftUIのメリットをよりよく活用していくために、アプリケーションの設計はどのようなものが良さそうか?

というのが今回のテーマです。

MVC, Clean Architecture, MVP, MVVM, Flux
など多くのアーキテクチャがある中でどのように考えられるでしょうか?

注意しておきたいところとして、今挙げたアーキテクチャはそれぞれカバーしている領域がどれも微妙に異なることです。
ものによっては画面に見える部分を中心に問題を解決することが考えられ、それ以外の部分は使用するプロジェクトに委ねられていることがあります。

ひとまず、SwiftUI + MVVMはどう?

ここで触れるMVVMとは、画面(View)やCellごとにViewModelを定義を行い、それぞれが担当するViewに表示するデータをバインディングするような設計を指します。

バインディングの方法は特に指定はありません。
Bi-directionalにすることもあればUni-directionalにすることもあると思います。

ViewModelを定義することで、ViewModelの担当範囲(コンポーネントごとなど)においてViewを安定的に更新していくことに注力できます。

Viewのための状態を細かく表現できることも強みとも言えるかもしれません。

次に、気をつけたいところはViewModelのライフサイクルです。
生成するタイミングと破棄するタイミングはどのように管理したら良いでしょうか。

UIKitアプリの場合、表示するUIViewControllerやUIViewに保持してもらうことで表示の役目を終えたときに一緒にViewModelも破棄してもらうことが可能です。

一方でViewModelは破棄せずに、また次の表示タイミングで使い回すようなキャッシュのような役割も可能です。

このようなことはUIKitがUIドリブンであるため可能ですが、一方でSwiftUIはデータ(状態)を元に画面の構築を行うことが大きな違いとなります。

UIKitアプリにおけるViewModelとViewの関係性

ViewModelの役割はSwiftUI.Viewがすでに担っているのでは?と考える

SwiftUI.Viewにおいて、

  • @ State
  • @ ObservedObject
  • @ EnviromentObject

これらで定義しているプロパティは自身の変更により画面の更新(var body: some View)が呼び出されます。
また、テキスト入力をプロパティにそのまま書き込みを行いたい場合は`@Binding`を使用することが出来ます。

UIKit + MVVMでやっていたことがSwiftUI.Viewで出来る、と捉えることができるかもしれません。

SwiftUI.Viewは画面に表示されるコンポーネントの実体ではなく、あくまでViewを構築するためのモデル。つまりViewModelという表現は当てはめられそうです。

ということで、SwiftUIはすでにMVVMパターンの上にあると捉えることにして、次はViewModelより下のレイヤーを考えていきます。

次に考えるところは、もっと大きな単位での状態を保持する役割

ViewModelの立ち位置

そのため、任意の情報をアプリケーション全体で同期を行い表示するという役割をViewModelだけで担当することは困難です。

もっと広範囲で全体的な状態を管理する部分について考えてみます。

Reduxを考える前に、ストアパターンを考える

ストアパターンのイメージ

ここまでくると、やはりReduxか!?となってしまいそうですが、その前にFluxアーキテクチャのコンセプトともいえるストアパターンを考えます。

Vue.jsのために開発されたFluxの実装であるVuexのドキュメントには次のように示しています。

もし、あなたが大規模な SPA を構築することなく、Vuex を導入した場合、冗長で恐ろしいと感じるかもしれません。そう感じることは全く普通です。あなたのアプリがシンプルであれば、Vuex なしで問題ないでしょう。単純な ストアパターン が必要なだけかもしれません。

引用文の中にある「ストアパターンが必要なだけかもしれません。」が大切なポイントだと考えており、Fluxという概念やRedux, Vuexという実装はストアパターンがベースとなっていることです。

このポイントをおさえておくことで、SwiftUIにReduxやVuexの実装を取り込み、何かしらの不都合が生じた場合はストアパターンの概念を忘れなければ適切にカスタマイズしていくことが可能だと思います。

🌲 Single state tree

Flux実装であるReduxやVuexはストアの数をひとつに限定し、アプリケーションのすべての状態をひとつのオブジェクト(Single state tree)として定義することを推奨しています。

以下、Vuexのドキュメントからの引用です。

Vuex は 単一ステートツリー (single state tree) を使います。つまり、この単一なオブジェクトはアプリケーションレベルの状態が全て含まれており、”信頼できる唯一の情報源 (single source of truth)” として機能します。これは、通常、アプリケーションごとに1つしかストアは持たないことを意味します。単一ステートツリーは状態の特定の部分を見つけること、デバッグのために現在のアプリケーションの状態のスナップショットを撮ることを容易にします。

Single state treeを定義し、SwiftUI.Viewによって表示に適した形に変換していくことで、アプリケーション全体のどの部分でも最新で正しいデータを表示することが簡単になります。

どの実装を参考にしたらいい? Redux? Vuex?

特に、Vuexにおける、mutationとactionの切り分けそれぞれの処理の内容を名前の定義とともに記述出来るところが気に入っています。

引用元 : アクション | Vuex

依存物はだれが所有する?

依存物とは主にデータレイヤーで必要となる、次のようなものです。

  • HTTPリクエストを行うAPIクライアントのインスタンス (URLSessionなど)
  • データベースなどのインスタンス
  • その他、なんらかのフラグなど

管理方法が気になる理由は、VuexでいうところのActionを発行する際に、APIクライアントへのアクセス方法がないとリクエストを投げることが出来ないからです。

依存物をシングルトンにしてしまうことで、staticな領域を通してActionからアクセス可能になりますが、シングルトンによる別の弊害も考えられます。
(見つかるサンプルは大体シングルトンにしていました。)

例えばTwitterクライアントのようなマルチアカウント機能を提供する場合にはどうなるんでしょう?
できればAPIクライアントをアカウントごとに生成し、トークン管理も任せたいところです。

Storeが上手いこと持つのか、依存物をなにかしらのdictionaryに詰めて状態に合わせて適切な依存物を取り出すのか。など方法は色々ありそうですが、ポピュラーと言えそうな事例はうまいこと見つけられませんでした。

Single state treeを採用するときに忘れてはいけないこと

それについてもRedux・Vuexのドキュメントでしっかりと触れられています。

Stateシェイプのノーマライズ

特にサーバーから受け取ったデータはデータベースに格納するように正規化してStateに格納しておくとパフォーマンスが下がりづらい状態を作ることが出来ます。

このような理由からReduxやVuexにはORMが拡張として用意されています。

正規化についてはReduxのドキュメントから詳細を得ることが出来ます。

適切な粒度でのメモ化

こちらについてもReduxドキュメントにて詳細を得ることが出来ます。

CoreDataやRealmと連携が必要になったら?

永続化はアプリによってはユーザー体験を高めるための重要なポイントです。
CoreDataを使用することを例に考えてみます。

アイデア1 : NSManagedObjectからstructに変換を行う

そのまま考えると、NSManagedObjectを値型(struct)に変換してStateに載せていくことが良さそうです。
NSManagedObjectが更新されるたびに、この変換とStateの更新を行います。

この方針には懸念点があり、それはNSManagedObjectから値を取り出すことは一定のコストがかかることです。

NSManagedObjectが持つデータはプロパティとして公開されていますが、実際にはメモリに読み込まれているかどうかはケースによります。
読み込まれていない場合はプロパティアクセス時にディスクIOのコストが発生します。

場合によりますが、レコード数とレコードが持つプロパティ次第ではNSManagedObjectから値型への変換コストは無視できないものになることは覚えておきたいポイントです。

アイデア2 : Storeとは別にCoreDataへアクセスを行う

例えば、アプリが持つ機能のなかで、チャット機能のみがCoreDataを用いているのであれば、チャット機能を表示する画面のみCoreDataとStoreを利用して表示を行うように設計を考えます。

これでパフォーマンスの問題からは逃れることは出来ますが、状態管理は分裂してしまうので、Single-source-treeのメリットのひとつである、「問題が発生した時のデバッグの行いやすさ」は薄れてしまいます。

このあたりは、Firebaseを使ったアプリ開発の事例が増えてきているので、そのあたりから色々経験談をもらうことができるかもしれません。

SwiftUIにうまく合いそうな設計を考えていくために頼りになりそうな参考は?

特にWebアプリケーションではポピュラーになっているReactやVueはすでにこれを採用しています。

SwiftUIはReactに似ている部分が多く、Reactにおけるプラクティスは非常に参考になります。
Vueも近い部分はあると思いますが、Viewの定義の仕方は少し異なる部分があるかもしれません。

iOSにおいても、FacebookはComponentKitを活用していたり、FlutterやReactNativeにおいても事例はたくさんあります。

ここまで考えてみての感想

一方で、データの反映に関しては、UIKitアプリとSwiftUIアプリで登場する役割を整理して紐付けることが出来たので、既存のUIKitアプリの設計の改善にも役立ちそうですし、将来来るかもしれないSwiftUIへの載せ替えに対応も出来るかもしれません。

エウレカが開発を行うPairs(ペアーズ)では将来に向けたアーキテクチャ改善を検討し、少しづつ実行に移しています。


おまけ

Redux実装を実現するにあたり、必要と考えられるツールをいくつかのモジュールに分割して提供するようにしています。既存のUIKitベースのアプリケーションで使うことを想定したViewModel実装も考慮しています。

  • Store
  • ViewModel — UIKitアプリのためのViewとStoreを接続するためのレイヤ
  • ORM — State上でEntityを正規化して管理するためのツール
  • RxExtension — UIKitアプリかつCombineが使えない時のRxSwiftを利用したバインディング用のツール

Eureka Engineering

Learn about Eureka’s engineering efforts, product developments and more.

Muukii (Hiroshi Kimura)

Written by

iOS Engineer & Head of Development, Pairs Global at Eureka, Inc. https://twitter.com/muukii_app

Eureka Engineering

Learn about Eureka’s engineering efforts, product developments and more.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade