このエントリーは読者としてスマートフォンアプリ開発者とWebフロントエンドエンジニアを想定して書いています。
CROSS2016に出るので、最近の自分の考えを整理しておく。
最近ReduxのSwift実装であるReSwiftを使って開発している。使った感想なども最後の部分に書いたけれど、このエントリーの本題はアプリの状態管理の話。
iOS、Android共にアプリを実装しようと思うと大抵シングルトンが必要になる。各ViewController内をまたがってデータを共有したいというユースケースが多いからだ。例えば
UserManagerLikesManagerBookmarkManagerなどなど。もちろんアプリの内容によってこれらの顔ぶれは違ってくると思うけれど、大抵UserManager以外にも一つ二つはシングルトンが存在していることと思う。これらはどのViewControllerから呼びたいし、状態を一意に保ちたいものたちだ。DIでインスタンス化を制御することはできるが、結局内部的にはシングルトンである。
自分はシングルトンを増やすことには抵抗があった。シングルトンは様々なところから使われ、内部状態が常に書き換わっていく。これが増えていくと、結果的にアプリの内部状態がどうなっているのか把握できなくなっていく。今UserManagerがどんな状態にあるのか(初回の起動なのか?ログインしているか?ユーザー情報の更新は終わったか?)を想像しながらコーディングやデバッグをすることになり、これは結構なストレスである。もちろん今までこれをずっとやってきているわけだが…。
しかしアプリ内でデータを共有したい、データを一意に保ちたいというニーズはなくならない。であれば、アプリは全体が大きなシングルトンであると考えてしまったほうがいいのではないか。ReduxのStoreがそんな思想で作られているかは知らないが、結果的にはそういう作り方を強制される。自分にはこの制約は結構しっくりきている。
野放図に作られるシングルトンは更新のための規約を持たない。どこからでも呼び出してメソッドを叩けば更新される。また、更新や状態の読み出しのためのメソッドの命名も実装者依存なので、結果的にはregisterUser,isLoggedIn,addLikeなどの名前が増えて行く(実装者にしても別に名前を混乱させる意図はなく、オブジェクトに注目して命名すれば自然とこうなると思う)。
ReSwiftのREADMEから転載
Fluxでは変更をActionに集約するという強い制約がある。すべてのActionはなんらかの状態更新を行うために発行されるので、ActionをLoggerに流していればアプリの起動時からの状態更新をすべて追うことができる。今の所は時々デバッグに使っているくらいなのだが、これをクラッシュ時に送信するというアイデアもあるようなので試してみたいと思っている。
別の効能としては、内部状態が実装者である自分の制御下にあるという、ある種の全能感が得られていてとても気分が良い。状態に起因する不具合があっても、必ず追いかけられるという安心感はとても心を落ち着かせてくれる。
感情的な部分を取り上げたが、内部状態が制御下にあるということは、開発メンバーが加わったり変わったりした際に開発がスケールすることに繋がるはずだ(まだその場面に直面していないので断言はできないけれど)。その辺りは大勢でステートルフなGUIをメンテナンスするという問題に直面したFacebook発祥のフレームワークだなという印象である。規約に沿って書けば誰でも内部状態が管理できる仕組みになっている。
結局の所、ある程度複雑化したクライアントサイド開発とは、状態遷移をいかに管理するかということに尽きるのではないかという気がしている。ユーザーからの入力、サーバーからのGETやPOST、様々なイベントがアプリの内部状態を変化させる。このイベントとそれによって起こる更新処理をどうモデリングして実装に落とすのかでアプリの複雑さは変わってくる。モデリングというと偉そうだが、要するに状態をすべて書き出してその遷移を記述するということである。
もちろん方法はFlux系だけではない。例えば状態遷移をステートマシン(有限オートマトン、FSM)として記述するというアイデアもある。今やっている開発の中でReSwiftと並行してSwiftStateも試してみたのだが、ログイン処理などはステートマシンで記述すると読みやすく書けそうだった。ステートマシンを使う手法はゲーム方面ではよく使われていそうなイメージがある(未経験なので詳細は不明)。
状態遷移の管理という観点で捉えると、Flux、ステートマシン、MVVM、Promise、FRPは対象としている状態の大きさの違いなのかもしれないとも思う。Fluxはアプリという大きな単位で状態を管理するためのアプローチである。Promiseはもっと小さな単位の状態遷移、ステートマシンはその中間くらいというイメージを持っている。PromiseやObservableはまとめることでより大きな状態遷移を作ることもできるので、小さな状態遷移を積み重ねて大きな状態遷移を作るものとして考えても面白い。
増え続ける状態と、その遷移をどうモデリングして管理していくかというのがアプリ開発の課題であると考えている。
しかし状態遷移をすべて書き出すプログラミングは、今までのように状態遷移を意識しないでプログラムを書いていた時よりも大変になる。自分がReSwiftを使っていて感じている問題を以下に書き出しておく。
現在進行形で実験している最中なので、特に今すぐReSwiftいいよと勧めるつもりはないけれど、この方向性はしばらく掘ってみたい。規模の大きいアプリを構築するために、状態を管理する仕組みが必要というのは今後も出てくる話題だと思う。
アプリ開発と状態遷移の管理 - ninjinkun's diaryシングルトンが状態を持つというのは悪夢のシナリオでは?
2016/02/03 01:05
@qtamaki 悪夢なのですが、グローバルな状態を持たずにクライアントを作るのは難しいと思います。少しでもましにしたいと思ってFluxでの管理を試しているところです
— ninjinkun (@ninjinkun)2016年2月2日
アプリ開発と状態遷移の管理 - ninjinkun's diaryそれ、シングルトンではなくて、グローバル変数では?スコープがグローバルであることは、シングルトンの要件では無いような……
2016/02/03 09:09
仰るとおり、このエントリではシングルトンをグローバル変数と同義で使っています。言いたかったのはグローバルな状態+手続きなので、リポジトリと言った方が正確だったかもしれません。ただ自分の経験から言うと、クライアントではこの種のリポジトリはシングルトンとして実装されるケースが多かったので、クライアント開発者がイメージしやすい言葉を使いました。
ReSwiftを使ったアプリ「RIDE」がリリースされました。このエントリーで考察したことが実装に反映されているアプリになります。見た目は普通ですが、ウォッチリストの同期部分などを見て頂けるとReSwiftの恩恵がわかるかもです。
ReSwiftについて発表した資料を公開しました。
その後Reduxで小さなアプリを作る実験をやってました。個人アプリだと要件は合わないと思います。
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。