SwiftでReduxをさわってみる


## はじめに

[potatotips勉強会 #23](http://connpass.com/event/21750/) で @izumin5210 さんが"Predictable state container" and Data Bindingのタイトルで, [Redux](https://github.com/rackt/redux)にインスパイアされて開発したAndroid向けの[Redux](https://github.com/rackt/redux)実装の[Droidux](https://github.com/izumin5210/Droidux)がとても興味深かったので、Swiftでも[Redux](https://github.com/rackt/redux)にチャレンジしてみることにしました。

[Redux](https://github.com/rackt/redux)は複雑になりがちなアプリの状態やデータを然るべきルールの元で管理することで、アプリ開発の複雑性を軽減し品質とメンテナンス性のあるコードへと寄与してくれます。この考え方はJavaScriptのアプリ(SPA)に留まらずiOSアプリでも通ずるように感じました。

Swiftでの[Redux](https://github.com/rackt/redux)実装を探したところ３つほど見つかりました。どれもGithubStarはまだ一桁台です。今回は[Aleksion](https://github.com/Aleksion)さん作の[ReduxSwift](https://github.com/Aleksion/reduxSwift)をさわってみます。[ReduxSwift](https://github.com/Aleksion/reduxSwift)はstateのsubscribeに[RxSwift](https://github.com/ReactiveX/RxSwift/)を活用しています。

- [Aleksion/reduxSwift](https://github.com/Aleksion/reduxSwift)
- [rob-brown/SwiftRedux](https://github.com/rob-brown/SwiftRedux)
- [mwise/Redux.swift](https://github.com/mwise/Redux.swift)


## Reduxについて

> Redux is a predictable state container for JavaScript apps.
> ReduxはJavaScriptアプリケーションのための予測可能な状態コンテナです。

> Reduxは以下の3原則に則って状態変化の流れを制限することで、状態を管理できるようにしています。

### Three Principles (3原則)

#### Single source of truth

> アプリケーション全体の状態(state)はツリーの形で１つのオブジェクトで作られ、１つのストアに保存される

#### State is read-only

> 状態を変更する手段は、変更内容をもったactionオブジェクトを発行して実行するだけ

#### Mutations are written as pure functions

> アクションがどのように状態を変更するかを「Reducer」で行う

### データフロー

Reduxのデータフローはシンプルな一方向のサイクルで表現されます。データ更新の手続きを「Reducer」という１つの窓口に集約することで、いつどのようにデータが更新されるのか把握しやすくなります。

![名称未設定.jpg](https://qiita-image-store.s3.amazonaws.com/0/4943/9e395770-9217-cd64-e8a6-78124c1ff5fb.jpeg "名称未設定.jpg")


図は [Introduction to Redux // Speaker Deck](https://speakerdeck.com/axross/introduction-to-redux) からお借りしました。

### ドキュメント

Reduxの詳細については本家のサイトやドキュメントを参照ください。和訳くださっている方がいるのでそちらもどうぞ。

本家

- [rackt/redux](https://github.com/rackt/redux)
- [Redux Read Me](http://rackt.org/redux/index.html)

翻訳

- [Redux入門 1日目 Reduxとは(公式ドキュメント和訳) ](http://qiita.com/kiita312/items/b001839150ab04a6a427)
- [Redux入門 2日目 Reduxの基本・Actions(公式ドキュメント和訳)](http://qiita.com/kiita312/items/8f8d047e5cbd87399ccb) 
- [Redux入門 3日目 Reduxの基本・Reducers(公式ドキュメント和訳)](http://qiita.com/kiita312/items/7fdce94912d6d9c801f8)
- [Redux入門 4日目 Reduxの基本・Stores(公式ドキュメント和訳)](http://qiita.com/kiita312/items/377787c24efac64f2495)
- [Redux入門 5日目 Reduxの基本・Data Flow(公式ドキュメント和訳)](http://qiita.com/kiita312/items/ae3ce31521ad24dd699f)
- [Redux入門 6日目 ReduxとReactの連携(公式ドキュメント和訳)](http://qiita.com/kiita312/items/d769c85f446994349b52)

## 環境構築

以下の環境で行いました。

- Xcode7.1.1
- Swift2.1

新規プロジェクトを作成後、Carthageでライブラリをインストールします。

```shell
$ cat Cartfile
github "Aleksion/reduxSwift"
git "git@github.com:ReactiveX/RxSwift.git" "2.0.0-beta.2"
```

```shell
$ carthage update --platform ios
*** Cloning RxSwift
*** Cloning reduxSwift
*** Checking out RxSwift at "2.0.0-beta.2"
*** Checking out reduxSwift at "0.0.18"
*** xcodebuild output can be found in /var/folders/mg/ryg2v7mx28jbd1k_6xh0gmc00000gn/T/carthage-xcodebuild.yrb0dw.log
*** Building scheme "RxBlocking-iOS" in Rx.xcworkspace
*** Building scheme "RxCocoa-iOS" in Rx.xcworkspace
*** Building scheme "RxSwift-iOS" in Rx.xcworkspace
*** Building scheme "SwiftRedux" in SwiftRedux.xcodeproj
 ~/s/g/s/ReduxExample  cat Cartfile
```

Buildしたライブラリをプロジェクトに追加します。

![ReduxExample_xcodeproj.jpg](https://qiita-image-store.s3.amazonaws.com/0/4943/1a9d4dad-7560-27f0-d2d6-cfc72f5199a7.jpeg "ReduxExample_xcodeproj.jpg")


追加したライブラリをインストール時にコピーするようにします。

![ReduxExample_xcodeproj.jpg](https://qiita-image-store.s3.amazonaws.com/0/4943/730c4acf-f86f-b879-b558-9a83137fa7cb.jpeg "ReduxExample_xcodeproj.jpg")


## コーディング

SwiftReduxのUsageを参考にコーディングしました（ほとんどそのままです）。
Swiftで記述する場合JavaScript版に比べて以下の優位性があるかなと思いました。

- ActionとStateのデータを`let`で定義することでImmutableであることを明示できる
- ActionのTypeに任意の一意な文字列をラベルを定義する必要がない
 - Struct名が自動的にTypeとして設定される
- storeをsubscribeすることで得られるstateは型を有しているので、どのようなstateなのか判断できる


```swift
//
//  ViewController.swift
//  ReduxExample
//
//  Created by susieyy on 11/29/15.
//  Copyright © 2015 SUSIEYY. All rights reserved.
//

import UIKit
import SwiftRedux
import RxSwift

/**
 * This is a simple standard action. The only requirement is that an action complies to
 * the Action protocol. The SimpleStandardAction containts a strongly typed rawPayload
 * property. The protocol automatically assigns the rawPayload to the Actions payload
 * property. This removes the necessity of type casting whenever working with actions in * a reducer.
 *
 * There's also the StandardAction protocol, that requires the struc to have an
 * initializer. This is required if the bindActionCreators helper is to be used.
 */
struct IncrementAction: SimpleStandardAction {
    let meta: Any? = nil
    let error: Bool = false
    let rawPayload: Int = 1
}

/**
 * This is a simple reducer. It is a pure function that follows the syntax
 * (state, action) -> state.
 * It describes how an action transforms the previous state into the next state.
 *
 * Instead of using the actions.type property - as is done in the regular Redux framework
 * we use the power of Swifts static typing to deduce the action.
 */
func counterReducer(previousState: Int?, action: Action) -> Int {
    // Declare the reducers default value
    let defaultValue = 0
    var state = previousState ?? defaultValue
    
    switch action {
    case let action as IncrementAction:
        state = state + action.rawPayload
        return state
    default:
        return state
    }
}

/**
 * The applications state. This should contain the state of the whole application.
 * When building larger applications, you can optionally assign complex structs to
 * properties on the AppState and handle them in the part of the application that
 * uses them.
 */
struct AppState: State {
    let count: Int!
}

/**
 * Create the applications reducer. While we could create a combineReducer function
 * we've currently chosen to allow reducers to be statically typed and accept
 * static states - instead of Any - which currently forces us to define the
 * application reducer as such. This could possibly be simplified with reflection.
 */
let applicationReducer = {(state: State? , action: Action) -> State in
    // Optionally throw error if the given state isn't an AppState
    let appState = state as! AppState?
    return AppState(count: counterReducer(appState?.count, action: action))
}

// Create application store. The second parameter is an optional default state.
let store = createStore(applicationReducer, initialState: nil)

class ViewController: UIViewController {

    @IBOutlet weak var countLabel: UILabel!
    var disposable: Disposable? = nil

    override func viewDidLoad() {
        super.viewDidLoad()

        disposable = store.subscribe{ (state: State) in
            print(state)
            if let state = state as? AppState {
                self.countLabel.text = "\(state.count)"
            }
        }
    }
    
    deinit {
        // Dispose of the subscriber after use.
        disposable?.dispose()
    }

    @IBAction func incremntAction(sender: AnyObject) {
        store.dispatch(IncrementAction())
    }
}

```

## 動作

![11月 29, 2015 23:33.gif](https://qiita-image-store.s3.amazonaws.com/0/4943/31b27487-228b-ca4c-13bf-e18eca456e92.gif "11月 29, 2015 23:33.gif")



## Refs

#### Redux

- [rackt/redux](https://github.com/rackt/redux)
- [Redux Read Me](http://rackt.org/redux/index.html)


#### Swift Redux

- [Aleksion/reduxSwift](https://github.com/Aleksion/reduxSwift)
- [rob-brown/SwiftRedux](https://github.com/rob-brown/SwiftRedux)
- [mwise/Redux.swift](https://github.com/mwise/Redux.swift)

#### 翻訳

- [Redux入門 1日目 Reduxとは(公式ドキュメント和訳) ](http://qiita.com/kiita312/items/b001839150ab04a6a427)
- [Redux入門 2日目 Reduxの基本・Actions(公式ドキュメント和訳)](http://qiita.com/kiita312/items/8f8d047e5cbd87399ccb) 
- [Redux入門 3日目 Reduxの基本・Reducers(公式ドキュメント和訳)](http://qiita.com/kiita312/items/7fdce94912d6d9c801f8)
- [Redux入門 4日目 Reduxの基本・Stores(公式ドキュメント和訳)](http://qiita.com/kiita312/items/377787c24efac64f2495)
- [Redux入門 5日目 Reduxの基本・Data Flow(公式ドキュメント和訳)](http://qiita.com/kiita312/items/ae3ce31521ad24dd699f)
- [Redux入門 6日目 ReduxとReactの連携(公式ドキュメント和訳)](http://qiita.com/kiita312/items/d769c85f446994349b52)