ReSwiftを使ってみる
ReSwiftというRedux-likeなswiftライブラリがあります。面白そうなので使ってみました。
ReSwiftの基本概念
ReSwiftではState,Store,Action,Reducerの4つの概念があります。
State
いわゆる状態です。stateはActionによってその状態を変化させる事ができます。
Store
アプリケーション全体のStateを管理します。Actionを受け取るとReducerにそれを渡してStateを更新し、Stateのサブスクライバーに対して変化を通知します。
Action
Stateをどう変化させるかを宣言します。
Reducer
StateとActionから新しいstateを作成します。
試してみる
習うより慣れろで試しに使ってみましょう。Qiitaのapiを使って検索するだけという簡単なアプリを作ります。こちらのサンプルを参考にしています。 完成したものはこんな感じです。コードはこちらにあります。
Stateを作る
まずStateを作ってみます。QiitaAPIState
はQiitaのapiを叩いた結果のsarchResults
という状態を持ちます。sarchResults
はqiitaの記事のモデルPost
の配列となっています。Result型になっていますが以降無視します。
struct QiitaAPIState { var sarchResults: Result<[Post]>? } protocol HasQiitaAPIState { var qiitaAPIState: QiitaAPIState { get set } }
AppState
はアプリ全体のStateを実装します。先ほどのHasQiitaAPIState
をここで適合させます。アプリが他にもStateを持つ場合はそれぞれStateを作ってAppState
に実装していきます。
struct AppState: StateType,HasNavigationState,HasQiitaAPIState,HasQiitaSceneState { var navigationState = NavigationState() var qiitaAPIState = QiitaAPIState() var qiitaSceneState = SearchQiitaScene.State() init() { } }
Actionを作る
QiitaAPIState
をどのように変化させるかというActionを作成します。ここではPost
がどう変わるかを宣言します。
struct SetPostSearchResult:Action { let result:Result<[Post]> init(_ result:Result<[Post]>) { self.result = result } }
Reducerを作る
ReducerはActionのSetPostSearchResult
を使ってStateのQiitaAPIState
を変化させます。handleAction
内でActionがSetPostSearchResult
であればsetPostSearchResult
が実行されます。ここでsarchResults
が更新されます。
struct QiitaAPIReducer:Reducer{ typealias ReducerStateType = HasQiitaAPIState func handleAction(state: ReducerStateType, action: Action) -> ReducerStateType { switch action { case let action as SetPostSearchResult: return setPostSearchResult(state, result: action.result) default: return state } } func setPostSearchResult(var state:ReducerStateType,result:Result<[Post]>)->ReducerStateType { state.qiitaAPIState.sarchResults = result return state } }
Storeを作る
StoreはReducerとStateを保持します。先ほど作成したAppState
,QiitaAPIReducer
が初期化時に渡されています。Storeはアプリに一つだけ?になるようです。
let mainStore:MainStore = MainStore(reducer:CombinedReducer([NavigationReducer(),QiitaAPIReducer(),SearchQiitaScene._Reducer()]), appState: AppState(), middleware: [loggingMiddleware])
その他部品を作る
これでRedux的な部分は作成しましたのでこれらをUIViewController等の慣れ親しんだ所に流し込んでいきます。Stateの変化の通知を受け取るためにStoreSubscriber
に適合させ、subscribeします。
class ViewController:UIViewController,StoreSubscriber { let store:Store = mainStore override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) store.subscribe(self) } }
subscribeするとnewState
でStateの変化を受け取ることができます。
func newState(state:AppState){ if let searchResults = state.qiitaAPIState.sarchResults { switch searchResults { case .Success(let posts): self.posts = posts self.tableView.reloadData() case .Error(let error): print(error) } } }
最後にActionを発行する関数searchPosts
を追加します。ActionCreatorはその名の通りActionを作成するための処理を記述します。
検索結果が返ってくるとstore.dispatch()
でstoreへActionを渡します。ここでは検索結果の配列です。正常に動作してれば配列に検索結果の要素が入っているでしょう。
storeはこれを受け取るとReducerへStateとActionを渡します。この時点のStateは以前の検索結果です。
ReducerはActionで宣言された変化をStateへ適用し新たなStateを発行します。つまり以前の検索結果を新たな検索結果に置き換える事になります。
この新たなStateがサブスクライバーに通知されます。これが先ほどのnewState()
です。
struct QiitaAPIActionCreator{ func searchPosts(query:String)->ActionCreator { return {state,store in if query.isEmpty { store.dispatch(SetPostSearchResult(Result(value: []))) return nil } APIClient.search(query){result in store.dispatch(SetPostSearchResult(result)) } return nil } } }
最後に
Reduxという概念が何かもわからないまま使ってみました。
気になる点といえばstateが更新されると全てのサブスクライバーのnewState()
が実行される点でしょうか。
他にはStateが増えると複数のStoreが必要になるのか?という疑問がありますが、まずはReduxについて調べてみます。