はじめに
こちらはiOS その2 Advent Calendar 2016 6日目の記事です。
今年は仕事でiOSを触る機会がめっきりと減ってしまったのですが、
「やはり1年を振り返るのならiOS Advent Calendarは欠かせないでしょう」ということで投稿することにしました。
最近、筆者が仕事で着手し始めた Redux に関連するということで ReSwift について見ていきたいと思います。
タイトルが (2) になっているのは、
以前に興味を持って自主的に取り組んでみたReSwiftを勉強してみよう!(1)の続きという意味です。
しかし、Advent Calendarの記事でもあるので本記事のみで完結する形で書きたいと思います。
今回は公式GitHubに上がっているCounterExample-Navigation-TimeTravelを元にReSwiftを勉強していきたいと思います。
ReSwiftに出てくるモノと役割
ReSwiftに出てくるモノと役割について改めて見直しをしてみましょう。
- Store
- アプリ内で必ず1つの存在
- アプリの状態を管理する
- Stateを更新するための
dispatchを提供する- 言い換えれば
dispatch(action)をすることでStoreにStateの変更を知らせられる
- 言い換えれば
- Stateの状態を追えるように
subscribeを提供する- 言い換えれば
subscribe(listener)をすることでlistenerはgetStateを通してStateの状態を取得できる
- 言い換えれば
サンプルでは下記が該当します。
Storeの宣言は下記のようになります。
1 2 3 4 5 6 7 | |
このサンプルではReSwiftRecorderモジュールを利用しているため通常のStore宣言とは異なります。
ですが、重要なのは、reducerにAppReducer()を指定していることと、Storeが管理するStateの初期値をstate: nilとしているということです。
- State
- アプリの状態
サンプルでは下記の通りです。
1 2 3 4 5 | |
サンプルではCounterViewControllerでカウント数の増加および減少をさせる処理が実装されており、
tabBarControllerやButtonのアクションなどで表示される画面を切り替える処理が実装されているため上記のようにcounterとnavigationStateの2つでアプリの状態を表すと定義しています。
- Action
- 何をするアクションなのかを表すオブジェクト
typeプロパティを必ず持つ
- ActionCreator
- Actionを作成するメソッド
サンプルでは下記の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
サンプルのCounterAction.swiftのCounterActionIncreaseはActionおよびActionCreatorの役割を兼ねています。
- Reducer
- ActionとStateから新たなStateを作成して返す
- ポイントはStateを更新するのではなく、 新しく作成したState を返すということ
サンプルでは下記の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
サンプルでは、handleActionメソッドで「引数として受け取ったactionとstateから作成したAppStateを返却」しています。
カウントアップの処理がReducerに伝わった場合には、
counterReducerメソッドでそのActionに合わせて新たに必要なStateの情報を生成しています。
(カウントアップの場合はCounterActionIncreaseアクションなのでcounter += 1しています。)
ReSwiftに出てくるモノ同士の連携
1個1個のモノと役割については理解が進みました。
続いて、それらの連携について見ていきましょう。
そのためにはViewControllerを見ていくのがわかりやすいかなと思います。
カウントアップ・ダウンに見る連携
まずはCounterViewControllerですが、
「+」や「-」ボタンをタップすることで画面中央に表示されたカウントを増減させる画面です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | |
細かく見ていきましょう。
Storeの役割で説明した「Stateの状態を追えるようにsubscribeを提供する」に相当する部分が、
1 2 3 4 5 6 7 | |
になります。
viewWillAppearでsubscribeを実行することで、その画面が表示されるときにStateの監視を開始して、viewWillDisappearでunsubscribeを実行することで、その画面が非表示になるときにStateの監視を終了することにしています。
また、「Stateを更新するためのdispatchを提供する」に相当する部分が、
1 2 3 4 5 6 7 8 9 10 11 | |
になります。
ユーザが「+」ボタンをタップしたときにincreaseButtonTappedが呼び出されます。
そのときに上記アクションが実行されたことをdispatchを実行することでStoreに知らせています。
decreaseButtonTappedも同様です。
画面切り替えに見る連携
続いてtabBarControllerによる画面の切り替えですが、これもこのサンプルでは状態変化として扱っています。
ただし、ReSwiftRouterモジュールで役割を担っているので、これを利用すれば開発者が新たに書く部分は非常に少なくなります。
UITabBarControllerDelegateを利用してタブをタップしたタイミングをキャッチします。
そのときに、ReSwiftRouterのSetRouteActionを利用してタブによる画面切り替えのアクションをStoreに伝えています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
因みに、SetRouteActionは下記のように定義されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
中身を見てみるとCounterActionと同様にtoStandardActionを利用しているのがわかります。
デバッグしていくとわかりますが、tabBarControllerを通した画面の切り替えでは、カウントアップやカウントダウンは無関係であるためAppReducerではcountの状態を増減させることはありません。
変化するのはAppState.navigationStateのみです。
こちらも追っていくとわかるのですが、下記のNavigationReducerに処理が引き継がれています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
このように、NavigationReducerの中でNavigationState型のオブジェクトを生成して返しています。
なので、アプリの状態であるAppStateが持つのは、
- counter: アプリのカウント状態
- navigationState: アプリの画面状態
と言えます。
まとめ
相変わらず、(流行っていないというのもあるかもしれませんが…)ReSwiftに関する日本語の記事が少ないですね。
恐らく、Web業界のようにFluxやReduxが実際のサービスに取り入れられているとは言い難いのでしょう。
その理由として考えられるのは、iOSの場合はデフォルトとしてStoryboardやViewControllerというものが存在し、Appleもそれをそのまま利用することを推奨しているからかもしれません。
しかしながら、複雑なアプリが世の中で求められるようになるに従って『ViewControllerの肥大化』や『複数人での開発による統一性の崩れ』といった課題により真剣に考えざるを得なくなってきました
。
必ずしも、これらの課題を解決するためにReSwiftを使わなければならないということはないのですが、1つの方法論として知っておくことで選択肢も増えてきます。
ただ、WebでReact&Reduxを取り入れるのと同様に理解するためのハードルが高くもあるので現場で嫌がられることもあるかもしれません。
また、保守性として高いとは決して言えません。開発メンバーの入れ替えが発生したときにReSwiftを勉強するコストが発生します。
Webと違ってそこまで浸透しているとは言えないため、より導入が難しいと言えるでしょう。
ここにある種の矛盾が生じているわけですね…
筆者がちょうどReact&Reduxに触れる機会が増えてきたため、良い機会だと思って、iOSでのReSwiftの利用メリットなどを合わせて考えていきたいと思っています。
そろそろ個人でのアプリ開発も復活させたい気持ちもありますし、その際には積極的に導入してみようかな…
と言ったところで本日はここまで。