SwiftでReduxを実現するReSwiftを使ってみた

[ReSwiftでアプリの状態管理](https://speakerdeck.com/ninjinkun/reactive-swift-meetup)のスライドを見てReSwiftなるものがあると知り面白そうだったので使ってみました

当方、JSあまり触ったことがなく、FluxもReduxもよくわからん状態でReSwiftの実装を見て説明しているだけなので間違いなどあればご指摘ください

# Reduxとは
ReduxはJavaScripアプリケーションのための予測可能な状態コンテナです
Reduxは3原則に則って状態変化の流れを制限することで、複雑状態の管理を可能にします

### Reduxの3原則

* Single source of truth(ソースは１つだけ)
  * アプリケーション全体の状態(State)はツリーの形で１つのオブジェクトで作られ、１つのストアに保存される
* State is read-only(状態は読み取り専用)
  * 状態を変更する手段は、変更内容をもったactionオブジェクトを発行して実行するだけ
* Mutations are written as pure functions(変更はすべてpureな関数で書かれる)
  * アクションがどのように状態を変更するかを「Reducer」で行う

### Reduxの登場人物
#### Store
* Storeはアプリケーション内で1つだけ存在し、1つのアプリケーションの状態(State)を保持する
* StateへアクセスするためのgetState()を提供する
* Stateを更新するためのdispatch(action)を提供する
* リスナーを登録するためのsubscribe(listener)を提供する

#### State
* アプリケーションの状態を表す

#### Action
* Storeが保持しているStateの変更内容が記載されているオブジェクト
* Actionは`store.dispatch()`でStoreへ送られる

#### ActionCreator
* Actionを作成する

#### Reducer
* ActionとStateから、新しいStateを作成して返す
* Stateを更新することはせず、新しいStateのオブジェクトを作成して返す

### Stateを変更する処理の流れ
Stateの変更内容が記載されているActionを発行し、Storeが提供している`dispatch(Action)`メソッドを通してStoreにActionを送る
Storeは送られてきたActionと保持しているStateをReducerに渡し、Reducerが新たなStateを生成しStoreのがStateを保持する

ほとんど[こちらの記事](http://qiita.com/kiita312/items/49a1f03445b19cf407b7)を参考、引用させていただきました


# ReSwiftとは
上記で説明したReduxをSwiftで実現することのできるライブラリです
https://github.com/ReSwift/ReSwift

# 作ったサンプルアプリについて
ということでReSwiftを使って簡単なサンプルアプリを作ってみました
[ReduxSwiftQiitaClient](https://github.com/hachinobu/ReduxSwiftQiitaClient)

このアプリは以前投稿した[MVVMっぽい構成のデモアプリを公開してみる](http://qiita.com/hachinobu/items/6140417b77f0fec0f647)と概ね同じものをReSwiftを使って実装したものです

### 画面
画面数は4つで下記の画面が存在します

| ホーム（投稿一覧） | 投稿詳細 |ユーザー投稿一覧|ユーザー投稿詳細|
|:-----------:|:------------:|:------------:|:-----------:|
|![Simulator Screen Shot 2016.06.20 21.44.14.png](https://qiita-image-store.s3.amazonaws.com/0/27785/3a9c8aec-fc4a-7132-5c19-1afcb1a94dd0.png)|![Simulator Screen Shot 2016.06.20 21.44.19.png](https://qiita-image-store.s3.amazonaws.com/0/27785/5d5f0555-9e7b-3a16-3ef6-03c270e16158.png)|![Simulator Screen Shot 2016.06.20 21.44.24.png](https://qiita-image-store.s3.amazonaws.com/0/27785/7401d35a-6ed7-775e-b87e-03ef0037b441.png)|![Simulator Screen Shot 2016.06.20 21.44.37.png](https://qiita-image-store.s3.amazonaws.com/0/27785/e46527dd-456d-a331-9a8e-a84eee1cf7c7.png)|

### 画面ごとの要件や構成
* ホーム画面（投稿一覧）
  * Qiitaの全投稿一覧が表示される
  * 一度に読み込まれる投稿は20件
  * 最下部までスクロールすると次の20件を読み込む
  * セルをタップすると、タップした投稿の詳細画面へ遷移する
* 投稿詳細画面
  * 投稿の詳細が表示される
  * ユーザー名のリンクをタップすると、そのユーザーの投稿一覧画面に遷移する
  * ストックボタンをタップすると投稿をストックor解除できる
  * 本文はWebViewで表示されている
* ユーザー投稿一覧画面
  * 選択したユーザーの投稿一覧が表示される
  * あとはホーム画面（投稿一覧）と同じ
* ユーザー投稿詳細
  * 投稿詳細画面と同じだが、ユーザー名のリンクは存在しない

### 今回作成したサンプルアプリの動かし方
とりあえずサンプルコード見て理解したいという方は[ここ](https://github.com/hachinobu/ReduxSwiftQiitaClient)に今回作成したサンプルコードがあります

ビルドが通るようになる一連の作業としては

```
git clone git@github.com:hachinobu/ReduxSwiftQiitaClient.git
```
でcloneして、`cd ReduxSwiftQiitaClient`でプロジェクト内に移動してください
その後に`pod install`を叩いてライブラリを入れてください

また、サンプルアプリを動かすにはQiitaのアクセストークンが必要です
アクセストークンは、この[取得画面](https://qiita.com/settings/tokens/new)から発行できます
スコープは下記スクリーンショットの通り、`read_qiita`と`write_qiita`にチェックをつけて「発行する」ボタンから発行してください

![スクリーンショット 2016-06-20 22.41.11.png](https://qiita-image-store.s3.amazonaws.com/0/27785/f02a3703-148b-f578-c71d-f70ec25ca5e8.png)


アクセストークンが発行されたら、それをコピーしてReduxSwiftQiitaClientプロジェクト内にSecrets.plistという名前のplistファイルを作成して
Dictionary TypeでAccessToken: 'Qiitaアクセストークン' となるようにしてください
（下記スクリーンショットのhogehoge部分を先ほど取得したアクセストークンに差し替えてください）

![スクリーンショット 2016-06-20 22.44.52.png](https://qiita-image-store.s3.amazonaws.com/0/27785/20e08332-66d4-d3e6-f8d3-c45136088cda.png)

これでビルドして成功すればアプリを使うことができます

# ReSwiftを使った実装(サンプルアプリ)の説明
ReSwiftは当然Reduxの概念に基づいているのでStore,State,Reducer,Actionが存在します

アプリケーションの状態(State)を保持するStoreを生成する必要がありますが、StoreはStateとReducerを先に作っておかないと生成できないので、まずはホーム画面を例にしてStateを設計してみます

![Simulator Screen Shot 2016.06.20 21.44.14.png](https://qiita-image-store.s3.amazonaws.com/0/27785/3a9c8aec-fc4a-7132-5c19-1afcb1a94dd0.png)

### ホーム画面のState

ホーム画面の表示項目に必要なものやAPI通信などを考慮して設計してみました

```HomeState.swift
struct HomeState: ArticleListScreenStateProtocol {
    var pageNumber: Int = 1
    var articleList: [ArticleModel]?
    var errorMessage: String?
    var isRefresh: Bool = false
    var showMoreLoading: Bool = false
}
```


HomeStateの各プロパティの意味についてです

* **pageNumber**
  * 現在何ページ目のデータを読み込んだかの情報です。最下部までスクロールすると次のページの情報を読み込む必要があるためです
* **articleList**
  * APIの返り値のオブジェクトです。ホーム画面の表示項目に必要なタイトルやタグ、ユーザー情報などが格納されているオブジェクトです
* **errorMessage**
  * 通信エラー時のメッセージを格納します。
* **isRefresh**
  * データをクリアしてデータ取得中かの判定フラグです
* **showMoreLoading**
  * ページング処理(次の◯件読み込み)中かの判定フラグです


また、HomeStateが準拠している`ArticleListScreenStateProtocol`ですが、これは私が作成した独自のプロトコルですので、Stateを生成するうえで必要なものではありません

Stateを生成するだけなら`struct HomeState {`で問題ありません
`ArticleListScreenStateProtocol`については、後ほど説明しますので、一旦忘れてください


### ホーム画面のAction
Stateに変更を加えるのに必要なActionです
ActionはReSwiftで定義されているActionプロトコルに準拠する必要があります
ReSwiftのActionプロトコルはActionとして判定するために定義されているだけで中身はありません

```swift
public protocol Action { }
```

下記がHomeStateのAction群です

```HomeStateAction.swift

extension HomeState {
    
    struct HomeRefreshAction: Action {
        let isRefresh: Bool
        let pageNumber: Int
    }
    
    struct HomeArticleResultAction: Action {
        let result: Result<GetAllArticleEndpoint.Response, SessionTaskError>
    }
    
    struct HomeShowMoreLoadingAction: Action {
        let showMoreLoading: Bool
    }
    
    struct HomeMoreArticleResultAction: Action {
        let result: Result<GetAllArticleEndpoint.Response, SessionTaskError>
    }
    
}
```

ActionはStateの存在ありきのオブジェクトなのでHomeStateのextensionとして記載しています

* **HomeRefreshAction**
  * HomeStateの`isRefresh`,`pageNumber`をまとめて変更する情報を持つAction
  * データ取得(PullToRefresh含む)開始/終了時に発行

* **HomeShowMoreLoadingAction**
  * HomeStateの`articleList`もしくは`errorMessage`を変更する情報を持つAction
  * Result型の投稿一覧取得APIの結果（成功or失敗）情報が格納される
  * 投稿一覧APIのレスポンスを受け取った際に発行

* **HomeShowMoreLoadingAction**
  * HomeStateの`showMoreLoading`を変更する情報を持つAction
  * ページング処理(次の◯件読み込み)の開始/終了時に発行

* **HomeMoreArticleResultAction**
  * HomeStateの`articleList`に追加するデータを持つAction
  * ページング処理(次の◯件読み込み)の◯ページ目の投稿一覧APIのレスポンスを受け取った際に発行

### ホーム画面のReducer
次に現在のStateとActionをもとに新たなStateを生成するReducerです
ReducerはReSwiftのReducerプロトコルに準拠する必要あります

```Reducer.swift
import Foundation

public protocol AnyReducer {
    func _handleAction(action: Action, state: StateType?) -> StateType
}

public protocol Reducer: AnyReducer {
    typealias ReducerStateType

    func handleAction(action: Action, state: ReducerStateType?) -> ReducerStateType
}

extension Reducer {
    public func _handleAction(action: Action, state: StateType?) -> StateType {
        return withSpecificTypes(action, state: state, function: handleAction)
    }
}
```

上記のようになっているので、各Reducerは、Reducerプロトコルの`func handleAction(action: Action, state: ReducerStateType?) -> ReducerStateType`を実装すれば良いです

それではHomeStateを変更するActionをもとに新たなStateを生成するHomeReducerです

```HomeReducer.swift
import Foundation
import ReSwift

struct HomeReducer {
}

extension HomeReducer: Reducer {
    
    func handleAction(action: Action, state: AppState?) -> AppState {
        let state = state ?? AppState()
        var newState = state
        var homeState = newState.home
        
        switch action {
        case let action as HomeState.HomeRefreshAction:
            homeState.updateIsRefresh(action.isRefresh)
            homeState.updatePageNumber(action.pageNumber)
            
        case let action as HomeState.HomeArticleResultAction:
            switch action.result {
            case .Success(let articleList):
                homeState.updateArticleList(articleList.articleModels)
                homeState.updateErrorMessage(nil)
                
            case .Failure(let error):
                switch error {
                case .ResponseError(let qiitaError as QiitaError):
                    homeState.updateErrorMessage(qiitaError.message)
                default:
                    homeState.updateErrorMessage("通信処理でエラーが発生しました")
                }
            }
            
        case let action as HomeState.HomeMoreArticleResultAction:
            if let moreArticleList = action.result.value {
                homeState.appendArticleList(moreArticleList.articleModels)
            }
            
        case let action as HomeState.HomeShowMoreLoadingAction:
            homeState.updateShowMoreLoading(action.showMoreLoading)
            
        default:
            break
        }
        
        newState.home = homeState
        return newState
    }
    
}
```

Reducerの`handleAction`メソッドにActionと現在のStateが引数で渡されます
引数、`state`の型や返り値である`AppState`はアプリケーション全体のStateです
Reduxの原則である

>* Single source of truth(ソースは１つだけ)
>  * アプリケーション全体の状態(State)はツリーの形で１つのオブジェクトで作られ、１つのストアに保存される

の部分に当てはまるもので、後述しますが、AppStateがHomeStateなど本アプリを構成する上で必要なState群を持ちます
HomeStateだけ作った現在だと下記です

```AppState.swift
import Foundation
import ReSwift

struct AppState: StateType {
    
    var home = HomeState()
    
}
```

HomeReducerの`handleAction`メソッド内の説明を続けます

```swift
let state = state ?? AppState()
var newState = state
var homeState = newState.home
```

Reducerはあくまでも現在のStateを更新するのではなく、新たにStateを作るので上記コードになっています
そして発行されたActionを判別して、新たに生成したStateに変更を当てていきます

```swift

        switch action {
        case let action as HomeState.HomeRefreshAction:
            homeState.updateIsRefresh(action.isRefresh)
            homeState.updatePageNumber(action.pageNumber)
            
        case let action as HomeState.HomeArticleResultAction:
            switch action.result {
            case .Success(let articleList):
                homeState.updateArticleList(articleList.articleModels)
                homeState.updateErrorMessage(nil)
                
            case .Failure(let error):
                switch error {
                case .ResponseError(let qiitaError as QiitaError):
                    homeState.updateErrorMessage(qiitaError.message)
                default:
                    homeState.updateErrorMessage("通信処理でエラーが発生しました")
                }
            }
            
        case let action as HomeState.HomeMoreArticleResultAction:
            if let moreArticleList = action.result.value {
                homeState.appendArticleList(moreArticleList.articleModels)
            }
            
        case let action as HomeState.HomeShowMoreLoadingAction:
            homeState.updateShowMoreLoading(action.showMoreLoading)
            
        default:
            break
        }
        
        newState.home = homeState
        return newState
    }
```

渡されたActionをどのActionなのか識別して、HomeStateを更新しています
HomeStateの`updateXX`メソッドは、HomeStateを更新する為のメソッドです
このメソッドはHomeStateが準拠していた`ArticleListScreenStateProtocol`に実装されているものです

実装を見ればわかりますが

```swift
homeState.updateIsRefresh(action.isRefresh)
```

は

```swift
homeState.isRefresh = action.isRefresh
```
と同じです

なぜ、`ArticleListScreenStateProtocol`を作成したのかというのは後ほど説明するとして、HomeReducerが新たにアプリケーション全体のStateを生成し、Actionに応じてHomeStateを更新していることが分かったと思います


### Store
State,Action,Reducerを設計したので、これでStoreを作ることができます
ReSwiftで提供されている`Store`クラスのインスタンスを生成します

```AppDelegate.swift
import UIKit
import ReSwift

let mainStore = Store(reducer: CombinedReducer([LoadingStateReducer(), HomeReducer(), ArticleDetailReducer(), UserArticleListReducer(), UserArticleDetailReducer()]), state: AppState())

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
```

第一引数の`reducer`について、本サンプルアプリでは画面などの単位でStateを設計して、そのStateに対応するActionとReducerも分割して作ったので、分割して作成した複数のReducerをCombinedReducer構造体を使うことで、まとめています

第二引数の`state`には先ほど記載したAppStateを登録しています
ここまでの説明では、ホーム画面のみですが、AppStateの完成系は各画面のStateやアプリ全体に共通して使うStateがツリー構造で登録されています

```AppState.swift

import Foundation
import ReSwift

struct AppState: StateType {
    
    var loading = LoadingState() //statusbarインジケータのState
    var home = HomeState() //ホーム画面のState
    var articleDetail = ArticleDetailState() //ホーム画面で選択した投稿の詳細画面のState
    var userArticleList = UserArticleListState() //選択したユーザーの投稿一覧画面のState
    var userArticleDetail = ArticleDetailState() //ユーザーの投稿一覧で選択した投稿の詳細画面のState
    
}
```

これでアプリケーション内で1つだけ存在し、1つのアプリケーションの状態(State)を保持するStoreができました
Storeはグローバル領域で定義されているので、どこからでも`mainStore`でStoreにアクセス可能です


### ReSwiftの処理の流れ

ReSwiftの処理の流れを追うためにホーム画面の実装ファイルであるArticleListTableViewController.swiftを見ながら説明します

#### リスナーの登録と解除

まずはStoreが保持しているStateが新しいものに変わった時に通知を検知できるようにStoreにリスナーを登録します
Storeの`subscribe`メソッドで登録できます

```ArticleListTableViewController.swift
    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        mainStore.subscribe(self)
    }
```

リスナーの解除は`unsubscribe`メソッドを呼び出します

```ArticleListTableViewController.swift
    override func viewDidDisappear(animated: Bool) {
        super.viewDidDisappear(animated)
        mainStore.unsubscribe(self)
    }
```

登録するリスナーは必ず`StoreSubscriber`プロトコルに準拠していなければなりません
準拠するには`func newState(state: StoreSubscriberStateType)`メソッドを実装すれば良いです
引数の型である`StoreSubscriberStateType`型にはアプリケーション単一のStateであるAppStateを指定します

```ArticleListTableViewController.swift
extension ArticleListTableViewController: StoreSubscriber {
    
    func newState(state: AppState) {
        //StoreのStateが新たなStateに変わった時に呼ばれる
    }

}
```

これで、Storeが保持しているStateが新たに生まれ変わった際に検知できるようになりました

#### Actionの生成とdispatch

ホーム画面のデータをAPI通信して取得するActionをdispatchしている`refreshData`メソッドを見ていきます

```ArticleListTableViewController.swift
    func refreshData() {
        
        mainStore.dispatch(LoadingState.LoadingAction(isLoading: true))
        let refreshStartAction = HomeState.HomeRefreshAction(isRefresh: true, pageNumber: 1)
        mainStore.dispatch(refreshStartAction)
        
        let actionCreator = QiitaAPIActionCreator.call(generateAllArticleRequest()) { [weak self] result in
            
            let pageNumber = self?.homeState.pageNumber ?? 1
            let refreshEndAction = HomeState.HomeRefreshAction(isRefresh: false, pageNumber: pageNumber)
            mainStore.dispatch(refreshEndAction)
            mainStore.dispatch(LoadingState.LoadingAction(isLoading: false))
            
            let action = HomeState.HomeArticleResultAction(result: result)
            mainStore.dispatch(action)
        }
        
        mainStore.dispatch(actionCreator)
    }
```

まず、`LoadingState`を変更する`LoadingAction`を発行してStoreにdispatchしていますが、これはホーム画面というより全画面で共有するStateなので一旦説明を割愛します

```ArticleListTableViewController.swift
let refreshStartAction = HomeState.HomeRefreshAction(isRefresh: true, pageNumber: 1)
mainStore.dispatch(refreshStartAction)
```

次にHomeStateの`isRefresh`と`pageNumber`を変更する`HomeRefreshAction`を生成してStoreにdispatchしています

StoreはActionが送られると、middlewareを介して登録されている全Reducerの`handleAction`メソッドを呼び出します
(発行されたActionと現在のStateを引数に渡して)

実際に`HomeRefreshAction`がdispatchされた際にStateを変更するのはHomeReducerのswitch文の下記部分です

```HomeReducer.swift
    case let action as HomeState.HomeRefreshAction:
       homeState.updateIsRefresh(action.isRefresh)
       homeState.updatePageNumber(action.pageNumber)
```

HomeReducer以外の他のReducerではswitch文の条件を通過するだけです

Actionが全Reducerに送られ、そこで新しく生まれ変わったStateがStoreに保持されます
そしてStoreが登録されている各リスナーに新たなStateを通知します
すなわち、`func newState(state: AppState)`が呼ばれるということです

引き続きArticleListTableViewControllerクラスの`refreshData`メソッドを見ていきます

```ArticleListTableViewController.swift
    let actionCreator = QiitaAPIActionCreator.call(generateAllArticleRequest()) { [weak self] result in
        
        let pageNumber = self?.homeState.pageNumber ?? 1
        let refreshEndAction = HomeState.HomeRefreshAction(isRefresh: false, pageNumber: pageNumber)
        mainStore.dispatch(refreshEndAction)
        mainStore.dispatch(LoadingState.LoadingAction(isLoading: false))
        
        let action = HomeState.HomeArticleResultAction(result: result)
        mainStore.dispatch(action)
    }
    
    mainStore.dispatch(actionCreator)
```

ここでは、ActionCreatorが登場します
ActionCreatorはその名前の通りActionを生成します
ReSwiftのActionCreatorは下記のようにStateとStoreを引数に受け取って、オプショナルなActionを返すクロージャとして宣言されています

```swift
public typealias ActionCreator = (state: State, store: Store) -> Action?
```

ActionCreatorをStoreにdispatchすると、Storeが受け取ったActionCreator（クロージャ）を実行して、返り値のActionをもとにStateを更新するようになっています

```Store.swift
    public func dispatch(actionCreatorProvider: ActionCreator) -> Any {
        let action = actionCreatorProvider(state: state, store: self)

        if let action = action {
            dispatch(action)
        }

        return action
    }
```


本サンプルコードの、QiitaAPIActionCreatorのcallメソッドの返り値が`ActionCreator`になっています

```QiitaAPIActionCreator.swift
struct QiitaAPIActionCreator {
    
    static func call<Request: QiitaRequestType>(request: Request, responseHandler: (Result<Request.Response, SessionTaskError>) -> Void) -> Store<AppState>.ActionCreator {
        
        return { state, store in
            
            Session.sendRequest(request) { result in
                responseHandler(result)
            }
            return nil
            
        }
        
    }
    
    ....

}
```

このActionCreatorは汎用的にしたかったので、渡されたRequestへの通信処理を実行して、結果をコールバックに渡すだけで、Action自体は生成しません（常にnilを返します

通信処理が終わった時に呼び出し元であるコールバック側からActionを発行してStoreにdispatchするようにしています

ActionCreatorの役割としてこれで良いのか？という疑問は残りますが、ReSwiftのサンプルコードでnilのActionを返し、通信処理だけしているActionCreatorがいたので、とりあえずこれで良いかといった感じで作りました(;^_^

ReSwiftにAsyncActionCreatorなるものがあるのですが、それを使えば良かったのかもしれません

ホーム画面のActionCreatorのコールバック側で発行しているActionは`HomeRefreshAction`,`LoadingAction`,`HomeArticleResultAction`です

```swift
    let pageNumber = self?.homeState.pageNumber ?? 1
    let refreshEndAction = HomeState.HomeRefreshAction(isRefresh: false, pageNumber: pageNumber)
    mainStore.dispatch(refreshEndAction)
    mainStore.dispatch(LoadingState.LoadingAction(isLoading: false))
    
    let action = HomeState.HomeArticleResultAction(result: result)
    mainStore.dispatch(action)
```

`HomeRefreshAction`を生成してStoreにdispatch
`LoadingAction`を生成してStoreにdispatch
`HomeArticleResultAction`を生成してStoreにdispatch

しているので、それぞれのActionがReducerに処理されてStateが変わるたびにnewStateメソッドが呼び出されます

すなわち、Actionをdispatchした回数である3回、newStateメソッドが呼び出されるわけです

このデータフローを踏まえ、newStateメソッドではホーム画面に必要なStateの状態の組み合わせを考慮して処理を書く必要があります

ちなみに、サンプルコード内で使っている`homeState`プロパティは`mainStore.state.home`と毎度書くのが冗長に感じたので、

```ArticleListTableViewController.swift
    private var homeState: HomeState {
        return mainStore.state.home
    }
```

として簡潔な名前でアクセスできるようにしているだけのGetterです


ホーム画面の`newState`メソッドは下記のよう実装しています

```ArticleListTableViewController.swift

    func newState(state: AppState) {
        
        if homeState.hasError() {
            showErrorDialog()
            return
        }
        
        if homeState.isRefresh && homeState.pageNumber == 1 {
            expireCache()
        }
        
        reloadView()
    }

```

簡単に何をしているか説明すると

* `homeState`が通信エラーの文字列が保持している場合はエラーダイアログを出して処理終了
* 初回データ取得状態（引っ張って更新含む）の場合は画像のキャッシュデータ削除
* `homeState`の次の◯件読み込み中フラグに応じて、最下部Viewのインジケーターの表示制御
* `homeState`が投稿データを1件以上保持していて、初回データ取得状態（引っ張って更新含む）でない場合はTableViewのレンダリングなどをする

といった処理をしています


この処理で、きちんと通信処理が成功した場合にViewのレンダリングを走らせるには、HomeStateの`errorMessage`プロパティがnilでなければなりません
どこでnilにしているかというとReducerでやっています

通信処理の結果をもとに生成されるHomeArticleResultActionは、`Result<GetAllArticleEndpoint.Response, SessionTaskError>`のプロパティを格納したActionなのでReducerが成功or失敗を判定して、成功パターンの場合はHomeStateの`articleList`にデータを入れて、`errorMessage`はnilにしています

```HomeReducer.swift
　　　　case let action as HomeState.HomeArticleResultAction:
        switch action.result {
        case .Success(let articleList):
            homeState.updateArticleList(articleList.articleModels)
            homeState.updateErrorMessage(nil)
            
        case .Failure(let error):
            switch error {
            case .ResponseError(let qiitaError as QiitaError):
                homeState.updateErrorMessage(qiitaError.message)
            default:
                homeState.updateErrorMessage("通信処理でエラーが発生しました")
            }
        }
```

もしかするとReducerで判定するのではなく、ActionCreatorのコールバックの中で成功or失敗を判定して、HomeStateの`articleList`の変更専用のActionと`errorMessage`を変更する専用のActionに分けてあげるべきだったのでは？と思ったりしています

### ReSwiftのデータフローを踏まえて意識・注意したこと

注意点として、ActionをStoreにdispatchするたびにStateが変わり`newState`メソッドが呼ばれるので当然、dispatchするActionの順番が変われば、`newState`メソッドが呼び出された時のStateが違うので、きちんと考慮して無駄な処理が走らないようにを心がけました

例として、

* ホーム画面では`newState`メソッド内の処理で`初回データ取得状態（引っ張って更新含む）の場合は画像のキャッシュデータ削除`という条件があります

その条件を満たす状態にするには`HomeState.HomeRefreshAction(isRefresh: true, pageNumber: 1)`というActionを発行してStoreにdispatchすれば良いです

`HomeState.HomeRefreshAction(isRefresh: true, pageNumber: 1)`をdispatchして`newState`が呼ばれてキャッシュが削除されます

次にStatusBarのインジケーター表示制御のStateを変えるAction `LoadingState.LoadingAction(isLoading: true)`を発行した場合、HomeStateは先ほどと同じく、キャッシュ削除の条件を満たす状態なのでホーム画面の`newState`が呼ばれた時に、再び画像のキャッシュを削除してしまいます

なので、`HomeState.HomeRefreshAction(isRefresh: true, pageNumber: 1)`よりも先に`LoadingState.LoadingAction(isLoading: true)`をdispatchすべきですし、コールバックでdispatchするActionもまずはキャッシュ削除の条件を満たさない状態にするActionである`HomeState.HomeRefreshAction(isRefresh: false, pageNumber: pageNumber)`をdispatchしてから後続のActionを発行してdispatchするべきです


また、`viewWillAppear`で`mainStore.subscribe(self)`をして`viewDidDisappear`で`mainStore.unsubscribe(self)`をしていますが、これはホーム画面から詳細画面に遷移して、詳細画面のViewControllerが自身の対象となるStateを変えるためにActionを発行して、Storeにdispatchするたびに現在アクティブ表示でない、ホーム画面のViewControllerの`newState`メソッドも常に呼ばれるので、無駄にViewのレンダリングなどの処理が走ることになるのを避けています

ちなみに`viewWillAppear`で`mainStore.subscribe(self)`を呼び出した瞬間にStoreに登録されている全てのリスナーの`newState`メソッドが呼ばれるようになっています


# ReSwiftを使う上で悩んだところ
ReSwiftを使って実装する上で一番悩んだのは、**ViewやViewControllerを共通で使いたい場合にどうすれば良いのか**ということです

例えばこのサンプルアプリだとホーム画面とユーザーの投稿一覧画面はViewの構造もViewに食わせるデータ構造も同じです
違いは、食わせるデータがユーザー固有のものに絞られているかというだけです
（投稿詳細画面も同じですが今回は一覧をサンプルとして出します）

| ホーム（投稿一覧） |ユーザー投稿一覧|
|:------------:|:-----------:|
|![Simulator Screen Shot 2016.06.20 21.44.14.png](https://qiita-image-store.s3.amazonaws.com/0/27785/3a9c8aec-fc4a-7132-5c19-1afcb1a94dd0.png)|![Simulator Screen Shot 2016.06.20 21.44.24.png](https://qiita-image-store.s3.amazonaws.com/0/27785/7401d35a-6ed7-775e-b87e-03ef0037b441.png)|

ReSwiftを使っていない場合は、投稿一覧取得APIのRequestにuserIdのパラメータを指定するかしないかの違いだけなので、簡単に対応できそうです

仮にViewのデータ構造が全く同じで、Viewに食わせるデータ構造だけ違う場合の画面であったとしても、Viewに食わせるモデルのインターフェースを共通化するProtocolに準拠させることで解決できます

私が、ReSwiftの場合に悩んだのは、
Stateは使いまわせても(別インスタンスを作ってAppStateに登録すれば良い)、どのStateを変えるかというActionを共通化できなかったところです

例えば、ホーム画面とユーザー投稿一覧画面で同一インスタンスのStateを監視していたら、ユーザー投稿一覧画面に遷移してから、ホーム画面に戻ってくるとホーム画面の表示が先ほど表示したユーザー投稿一覧画面と同じ内容のものになっていまいます

なので当然ですが、同一のStateを監視するのは、アプリ内（画面間）で共有したいStateの場合だけです

変更させるべき対象のStateに対するActionを動的に生成してうまいことコントロールする層を作る必要があるんだろうなと感じました

今回のサンプルアプリでは ~~面倒臭くなって~~ 同じViewControllerを書いて、Actionを発行するところを変えるというイケテナイ実装になっています

最低でもViewController継承してAction発行する箇所をメソッドに切り出してoverrideすべきだったと反省
（しかしStateやActionが増えるごとにViewやViewControllerを継承するのか！？）

せめてViewに食わせたりする部分は共通化しようと思って苦肉の策で作ったのが、HomeStateが準拠していた`ArticleListScreenStateProtocol`なのです


# ReSwiftを使ってみて感想
* グローバルな領域にアプリの全ての状態を持つことに対して、どこからでもActionを発行して他の画面とか関係ないところの状態を変えることができるというのは、理論上はやらないと分かりつつも精神衛生上、不安な気持ちになった
* アプリ間で共有するようなところだけグローバルで持てればなとか思った
* アプリ間、画面間で共有したい状態をグローバルで持つので同期が楽
* 状態を持つ場所が明確であり、状態を変更する際の流れが一方行になることで曖昧さがなくなるのは凄く良い
* 状態変化が起きた際の処理を一箇所にまとめることができるので可読性が良いし処理が散らばらないのでデバッグしやすい
* 状態の再現やテストがしやすい
* レンダリングコストとかあまり気にする必要ないかもだけれど、Stateのどこが更新されたか差分を取得できる仕組み作ると更に良さそう
* 沢山の状態を持ち、その状態が複雑に絡み合うような大きなアプリで採用するには良さそうだなと感じた
* ホーム画面とかでPullToRefreshした時に`refreshData`メソッドを直接呼んじゃってるけど、実際にPullToRefreshしてるかの状態をStateに持たせてActionをdispatchしてあげるべきなんだろうな

# 今回やれていないこと
* middlewareの活用
* ReSwiftリポジトリのデモで使われている[ReSwift-Router](https://github.com/ReSwift/ReSwift-Router)や[ReSwift-Recorder](https://github.com/ReSwift/ReSwift-Recorder)が、どんなことを解決できるか調べていない

# 最後に
本当に簡単なサンプルアプリを作成した程度なのでReSwiftの良し悪しを語れるレベルにはないと思っています
~~そもそも私の作成したサンプルアプリではStateの設計とかReduxの概念に沿ってちゃんと作れているのか些か不安が残る~~


興味のある方は実際に触ってみると面白いと思います

ReSwiftの表面的な使い方としてお役に立てれば幸いです