ReSwiftを使って爆速でカウンターアプリを作る(初学者向け)

## ReSwiftを使ってみる
業務で携わっているアプリにReSwiftを導入することになりました。
ReSwiftもReduxも触ったことがないのでとりあえずサンプルアプリを作ってみます。
サンプルアプリはこれを丸パクリします。
僕はRedux初学者でしたが、ReSwiftを使うとあっという間にカウンターアプリを作ることができました。
https://github.com/ReSwift/CounterExample

### ReSwiftとは？
ReSwiftとは、SwiftでRedux風の実装を可能にするライブラリのことです。
ReduxとはFacebookの提唱したUIのstate(状態)を管理をするためのフレームワークであり、元はJSで使用されている考え方でした。それをiOSにも応用し、Swiftでも同様に使えるようにしたのがReSwiftです。
そのため、ReSwiftを使うにはReduxの概念を理解する必要があり、Reduxの概念を理解するには情報の多いJSの記事を読むと理解が深まりました。
こちらの記事がまとまっていて良いです。[Redux入門【ダイジェスト版】10分で理解するReduxの基礎](https://qiita.com/kitagawamac/items/49a1f03445b19cf407b7)

### 導入
導入は他のライブラリ同様CocoaPods経由で行うことができます。
他のライブラリ管理ツールを使用する場合の導入手順もこちらに書いてありました
http://reswift.github.io/ReSwift/master/installation.html
今回はCocoaPodsでインストールしてみます。

プロジェクトを作成し、そのプロジェクトのディレクトリまで移動します。
そこで以下のコマンドを入力します。

```
pod init
```

これでライブラリを管理するPodfileが作られます。
lsコマンドを打つとPodfileの存在を確認できます。
次にvimでPodfileの中身を編集していきます。
以下のコマンドを打ってPodfileを開きましょう。

```
vi Podfile
```
開くとこのように表示されます。

```
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'ReSwiftSample' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for ReSwiftSample

end
```
「i」を押してインサートモードの状態にして、インストールするライブラリを記入します。

```
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'ReSwiftSample' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for ReSwiftSample

pod 'ReSwift'

end
```

「pod 'ReSwift'」が記入できたら、「esc」キーを押してインサートモードを終了します。
その状態で「:wq」を押して保存すれば準備完了です。
以下のコマンドを実行してReSwiftをインストールしましょう。

```
pod install
```
インストールが完了するとxcworkspaceができます。<img width="578" alt="スクリーンショット 2019-05-05 10.19.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/c0ec3670-415c-3cc4-0df6-023639161637.png">
これで導入は完了です。

### 準備
まず画面を作ってそれからCount機能を実装します。
適当に数字のラベルとカウントアップボタン、カウントダウンボタンを乗せて下さい。
<img width="255" alt="スクリーンショット 2019-05-05 22.42.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/7631499b-be93-da32-a8d9-3b2ec2603f6d.png">
それらの部品をViewControllerとOutlet接続したら準備OKです。

```Swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var counterLabel: UILabel!
    
    @IBAction func countUp(_ sender: UIButton) {
    }
    
    @IBAction func countDown(_ sender: UIButton) {
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
}
```

### Reduxの要素
* Action
* Store
* State
* Reducer

これらの要素を使ってCounterアプリを作ります。

#### Action
ActionとはStateの変更を指示するものです
Action自体は機能を持たないただのデータであり、通常structやenumで定義されます。
要は「今からこんな状態になるから頼むわ」とReducerにお願いするものだと思ってます。
ReSwiftのアクションは全てActionプロトコルに準拠しています。

```Swift
import Foundation
import ReSwift

struct CounterActionIncrease: Action {}
struct CounterActionDecrease: Action {}
```

#### State
Stateとはアプリの状態のことです。
structで定義され、stateTypeプロトコルに準拠して定義します。
StateにはそのStateの管理する変数を定義しておきます。
今回のカウンターアプリの場合は数字ですね。

```Swift
import ReSwift

struct AppState: StateType {
    var counter: Int = 0
}
```

#### Reducer
Reducerのポイントはただ一つ、「actionと現在のstateを受け取り、新たなstateを生成する」ことです。
ReduxにおいてReducerはアプリの状態を変化させられる唯一の場所になります。
Reducerファイルには直にメソッドを定義します。


```Swift
import ReSwift

func counterReducer(action: Action, state: AppState?) -> AppState {
    var state = state ?? AppState()
    // 引数で受け取ったactionを判定し、処理を行う
    switch action {
    case is CounterActionIncrease:
        state.counter += 1 // AppStateにはInt型の変数Counterを定義している
    case is CounterActionDecrease:
        state.counter -= 1
    default:
        break
    }
    return state
}
```

#### Store
StoreはStateを保持する唯一のインスタンスです。
一つのアプリに一つだけ使用されるため、どこからでも利用できるAppDelegateに定義します。

```Swift
import ReSwift

let mainStore = Store<AppState>(
    reducer: counterReducer, // アプリの利用するReducer
    state: nil // 初期State
)

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

```

アプリ全体のStateは慣例としてAppStateと定義されるみたいです。

### Controllerに購読させる
ここまでAction、State、Reducer、Storeを用意してきたので、Reduxを実装する準備ができました。
ViewControllerではStoreに自身を購読させ、状態の変化を管理させます。

```Swift
import ReSwift

class ViewController: UIViewController, StoreSubscriber { // StoreSubscriberに準拠するの忘れがち

    @IBOutlet weak var counterLabel: UILabel!
    
    @IBAction func countUp(_ sender: UIButton) {
        mainStore.dispatch(CounterActionIncrease())
    }
    
    @IBAction func countDown(_ sender: UIButton) {
        mainStore.dispatch(CounterActionDecrease())
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        mainStore.subscribe(self)
    }
    
    func newState(state: AppState) {
        counterLabel.text = "\(mainStore.state.counter)"
    }
}

```
ポイントは３つです。

* subscribe
* dispatch
* newState

#### subscribe
subscribeとは購読という意味です。
StoreにViewControllerを購読させると、ViewContollerの変化をStoreが検知してくれます。
Storeに購読させるにはStoreSubscriberプロトコルに準拠する必要があります。
これを忘れてて30分くらいハマりました。

```Swift
mainStore.subscribe(self)
```

基本的にViewWillAppearなどのViewが表示される直前の処理で行います。
画面遷移などで購読を終了する時はunsubscribeを行います。

####dispatch
dispatchとは「Storeにアクションをわたしますよー」という意味です。
StoreにはReducerが入っており、ReducerはActionを受け取って新たなStateを返します。

```Swift
@IBAction func countUp(_ sender: UIButton) {
    mainStore.dispatch(CounterActionIncrease())
}
```
今回のアプリではこのdispatchで渡しているActionをReducerの中で判定し、カウントのアップとダウンを行なっています。
こちらのコードを思い出すとなるほどなとなります。(僕はなりました。)

```Swift
switch action {
case is CounterActionIncrease:
    state.counter += 1 // AppStateにはInt型の変数Counterを定義している
case is CounterActionDecrease:
    state.counter -= 1
default:
    break
}
```
#### newState
stateが新しくなった時に実行されます。
StoreSubScriberのextensionで定義されています。

```Swift
extension StoreSubscriber {
    // swiftlint:disable:next identifier_name
    public func _newState(state: Any) {
        if let typedState = state as? StoreSubscriberStateType {
            newState(state: typedState)
        }
    }
}
```
### 完成
これで一通りカウンターアプリを実装できました。
実行してみます。
<img width="255" alt="スクリーンショット 2019-05-06 0.43.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311282/53e5a90d-93e9-778c-3a20-48e70d4b46ce.png">
できました。

### おまけ
これだけだと流石にコピペが過ぎるのでちょっと機能追加してみます。
カウントリセットボタンを実装します。
リセットのActionを追加してViewControllerとReducerに処理を追加するだけです。

```Swift
import ReSwift

struct CounterActionIncrease: Action {}
struct CounterActionDecrease: Action {}
struct CounrerActionReset: Action {}
```

```Swift
import ReSwift

func counterReducer(action: Action, state: AppState?) -> AppState {
    var state = state ?? AppState()
    // 引数で受け取ったactionを判定し、処理を行う
    switch action {
    case is CounterActionIncrease:
        state.counter += 1 // AppStateにはInt型の変数Counterを定義している
    case is CounterActionDecrease:
        state.counter -= 1
    case is CounrerActionReset:
        state.counter = 0
    default:
        break
    }
    return state
}
```

```Swift
import ReSwift

class ViewController: UIViewController, StoreSubscriber { // StoreSubscriberに準拠するの忘れがち

    
    @IBOutlet weak var counterLabel: UILabel!
    
    @IBAction func countUp(_ sender: UIButton) {
        mainStore.dispatch(CounterActionIncrease())
    }
    
    @IBAction func countDown(_ sender: UIButton) {
        mainStore.dispatch(CounterActionDecrease())
    }
    
    @IBAction func countReset(_ sender: UIButton) {
        mainStore.dispatch(CounrerActionReset())
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        mainStore.subscribe(self)
    }
    
    func newState(state: AppState) {
        counterLabel.text = "\(mainStore.state.counter)"
    }
}
```
できました。
次はReSwiftで画面遷移とか非同期処理ありのアプリを作りたいです。

### 参考
ReSwift
* [ReSwift Reference](http://reswift.github.io/ReSwift/master/)
* [ReSwiftを勉強してみよう！(1)](http://grandbig.github.io/blog/2016/09/03/reswift-1/)
* [ReduxとSwiftの組み合わせを利用したUIサンプル事例紹介](ReduxとSwiftの組み合わせを利用したUIサンプル事例紹介)
* [CounterExample](https://github.com/ReSwift/CounterExample)


Redux
* [Redux入門【ダイジェスト版】10分で理解するReduxの基礎
](https://qiita.com/kitagawamac/items/49a1f03445b19cf407b7)