読者です 読者をやめる 読者になる 読者になる

情報系人間のブログ

プログラミング、開発に関することを書いていきます。

ReSwiftを使ってみる

Swift

ReSwiftというRedux-likeなswiftライブラリがあります。面白そうなので使ってみました。

github.com

ReSwiftの基本概念

ReSwiftではState,Store,Action,Reducerの4つの概念があります。

State

いわゆる状態です。stateはActionによってその状態を変化させる事ができます。

Store

アプリケーション全体のStateを管理します。Actionを受け取るとReducerにそれを渡してStateを更新し、Stateのサブスクライバーに対して変化を通知します。

Action

Stateをどう変化させるかを宣言します。

Reducer

StateとActionから新しいstateを作成します。

試してみる

習うより慣れろで試しに使ってみましょう。Qiitaのapiを使って検索するだけという簡単なアプリを作ります。こちらのサンプルを参考にしています。 完成したものはこんな感じです。コードはこちらにあります。

f:id:reida:20160216235203p:plain

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について調べてみます。