対象読者
スケールしやすいアプリの設計に興味のあるiOSエンジニアの方を対象にしています。
Fluxの概要
Webフロントエンドの世界で、今ではよく利用されているFluxですが、モバイルアプリ界隈では現状そこまで広く認知されていないと思います。そこで、まずは簡単にFluxの概要を整理します。
Flux考案の背景
Fluxは「アプリケーションのコードベースが大きくなるにつれて極端に複雑化し、アプリケーションの状態が壊れやすく予測不可能なものになってしまう」といった問題を解決するために、Facebookによって考案されたアーキテクチャです。
数年前、状態管理が複雑になりやすいという問題意識を抱えていたWebフロントエンドの世界に、Reactを筆頭としたVirtual DOMの概念とそれを実装したライブラリが登場しました。Fluxは、Viewの差分更新が効率的に行えるVirtual DOMの特徴と非常に相性が良く、それまでと比べてシンプルに状態管理が行えるようになりました。現在ではスケーラビリティを求める、数多くのプロダクトで利用されるメジャーな存在です。
Fluxはあくまで設計上のアイディアであり、その実装はいくつかのライブラリや、それぞれのプロジェクトにおける独自実装として実現されています。
Facebookが公開しているfacebook/fluxというFluxの実装がありますが、本記事でFluxと書く場合は、コンセプトとしてのFluxを指します。
Fluxのコンセプトは「データの流れを単一方向にする」ことです。「Action」「Dispatcher」「Store」「View」のパートから構成されます。
- Action:Actionの種類とペイロードを持つ。Viewへの操作などをトリガーに発行される。
- Dispatcher:データフローのハブとなるSingletonなオブジェクト。
- Store:アプリケーションの状態管理とビジネスロジック。
- View:Storeの状態をもとに画面上の表示要素を構築する。また、ユーザー操作を受けてActionを発行する。
公式ドキュメントから引用した図にしたがって、アプリケーション内のデータの流れを簡単に説明します。
- Viewに対するユーザー操作などにより発行されるActionが、Dispatcherを通じてStoreに渡る。
- StoreはそのActionに応じて自身の状態を変化させる。
- Storeから変更通知を受けたViewはStoreのデータに基づいて再構築される。
この流れになります。また、「Actionを経由せず変化させることはできない」強い制限があります。このようにデータの流れを単一方向にすることで、Viewの取りうる状態が予測可能になり、再現性も高くなります。アプリ利用時に発行されたアクションを記録しておき、後でそれを再生することで同じ状態を再現する、といったことも比較的簡単に実現できるでしょう。
iOSアプリへのFluxの導入(1)
ここからは、FluxをiOSアプリ開発の設計に適用するメリットや、その具体的な方法を紹介します。
導入に至ったポイント
もともとWebの世界で生まれたFluxですが、先述の通り、その基本的なコンセプトは「データの流れを単一方向に制限する」ことです。これを単方向データフロー(unidirectional data flow)といいますが、このアイディアはプラットフォームをWebフロントエンドに限定せず、iOSアプリにおいてもスケーラビリティを向上させるために有用だと考え、導入に至りました。
導入対象は「freee for チーム」というアプリで、導入企業の従業員が手元で給与明細や勤怠登録を行うことができるものです。
Flux導入時期について
実際にはfreee for チームにFluxを導入したのは2年程前(2015年)になるのですが、当時はSwiftFluxと呼ばれる、弊社エンジニアが開発したライブラリをベースにしていました。ですが、これはfacebook/fluxをSwiftで再現したもので、もう少しSwiftやモバイル開発に最適化できそうな要素があり、それが課題でした。また、時間の流れから陳腐化してきていました。そこで、今年に入り改めて設計を見直した、といった経緯があります。
freee以外でも、iOSアプリへのFluxの導入事例はいくつか見つかるので、調べてみると参考になるでしょう。既存のライブラリを利用する場合は、Webフロントエンド界隈でとりわけ有名なFlux派生実装である、Reduxのアイディアをベースに実装されたReSwiftがよく利用される印象があります。ReSwiftではアプリのすべての状態を1つのStructをルートとする木構造のオブジェクトとして表現します。そのため、アプリの取りうる状態が大きくなればなるほど、そのオブジェクトのコピーコスト・メモリコストが増大するデメリットの懸念があり、今回はReSwiftの採用を見送りました。
現状、デファクトスタンダードといえるライブラリは他に見つかりませんでした。ただ、そもそもWebとiOSアプリではその開発言語やViewの性質、ライフサイクルも異なるため、Webで利用されている実装をiOSアプリでそのまま再現するのではなく、iOSアプリの特性に合わせて実装もチューニングしたほうが使いやすいものになると考えました。そこで、freee for チームでは既存のライブラリなどを利用せず、単方向データフローを実現するための小さなコンポーネントを独自実装することにしました。iOSアプリでFluxを採用する際のベストプラクティスや知見も少ない状態だったので、独自実装であれば柔軟に機能変更が行える、といった理由もあります。