ReSwiftを使うときに知っておくといいTips3選

# 前書き
最近AndroidでもiOSでもReduxアーキテクチャが気に入っており、アプリ開発時に(ごり押して)採用しているUroteaでございます。
Android向けReduxの記事も書いているので、Androidも書く人は読んでみてください。

# ReSwiftを使ってアプリを書いていて気が付いたTips集

機会があり、iOSアプリを書くことになりました。  
ログインしたユーザの権限によってボタンを非表示にしたり、レイアウトを変えたりする必要があるため、状態管理に強いReSwiftを選択しました。
ある程度作り終わった今、この選択は正解だったと思っています。
また、ViewとのbindingのためにRxSwiftも併用しましたが、こちらも採用してよかったと思っています。
そんな中で気が付いたTipsを共有したいと思います。

## この記事で書くこと
- ReSwiftを使うときに、知っておくと便利なこと
- RxSwiftとどう連携したか

## この記事で書かないこと
- ReSwiftの概念、使い方
- RxSwiftの概念、使い方

## 対象読者
- iOS開発の基本を知っている
- Swiftがある程度読める
- Reduxの基本を知っている
- ReSwiftの基本は知っている
- RxSwiftについてなんとなく知っている
- MVVMについてなんとなく知っている

# Tips1: Actionはenumで定義する

## 型で区切ることで管理を容易にする
Reduxの概念の一つ`Action`ですが、ReSwiftのreadmeには以下のように書かれています。

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

もちろんこれでもいいのですが、`Action`が100個くらいになると、どの`Action`がどのUIのイベントに対応しているのかの管理が困難になります。と言うか無理です。
ある程度なんらかの単位に切り分けて管理しなければ私の脳では処理ができませんでした。
私は**画面ごと**に分割することにしました(分割単位や粒度は場合によると思います)。

```Swift

enum Actions: Action {
    case loginViewActions(LoginViewActions)
    case mainViewActions(MainViewActions)
}

enum LoginViewActions {
    case loginButtonTapped
    case userNameTextChanged(userName: String)
}

enum mainViewActions {
    // 省略
}
```

このように分割すると
- それぞれの`Action`がどの画面に対応しているのか型で判別可能
- `Action`の名前衝突問題を気にしなくて良い

## Reducerでパターンマッチングが効く
さらに、`Reducer`でも力を発揮します。公式のreadmeでは以下のように書かれています

```Swift
func counterReducer(action: Action, state: AppState?) -> AppState {
    var state = state ?? AppState()

    switch action {
    case _ as CounterActionIncrease:
        state.counter += 1
    case _ as CounterActionDecrease:
        state.counter -= 1
    default:
        break
    }

    return state
}
```

こちらの欠点としては、新たな`Action`を追加したときに、`Reducer`に変更を加えるのを忘れてもコンパイルエラーにならないことです。
`Reducer`のテストは書くと思うので、大丈夫だと思いますが気が付けるに越したことはありません。
`enum`を採用すると以下のようになります。

```Swift
func counterReducer(action: Action, state: AppState?) -> AppState {
    var state = state ?? AppState()

    switch action {
    case loginViewActions(let action):
        switch action {
            case loginButtonTapped:
                // なんらかの処理
            case userNameTextChanged(let userName):
                // なんらかの処理
        }
    case mainViewActions(let action):
        switch action {
            // 省略
        }
    }
}
```

`enum`にすることで`default`句を消すことができ、新しい`Action`を増やすとコンパイルで弾かれるようになります。
また、どの部分でどの`Action`の処理をしているのかを見やすくなるので、可読性が向上します。

# Tips2: RxSwiftのdistinctUntilChangedを使用してViewに状態の変更を伝える

RxSwiftにはRxCocoaというViewと連携するときに便利なライブラリがついており、これを活用します。
今回はMVVMアーキテクチャを採用した場合のサンプルです。

```Swift

class LoginViewModel {
    private let loginButtonEnabledOutputStream = PublishRelay<Bool>
    let loginButtonEnabled: Signal<Bool> {
        self.loginButtonEnabledOutputStream.asSignal().distinctUntilChanged()
    }
}

extension LoginViewModel: StoreSubscriber {
    typealias StoreSubscriberStateType = AppState

    func newState(state: AppState) {
        self.loginButtonEnabledOutputStream(state.login.buttonEnabled)
    }
}
```

ポイントは`distinctUntilChanged()`です。ReSwiftのstateはstateが更新されるたびに呼び出されます。
上記の例ですと、`loginButton`の状態が更新されていなくても、state全体としては更新されていると`newState(state: AppState)`が呼び出されます。
ボタンを`enable`から`enable`にするなら問題はありませんが、`dialog`を表示する処理などをReSwiftに乗っけると、何かあるたびに`dialog`が表示されてしまいます。
`distinctUntilChanged()`を挟んでおくことで、false -> trueなど、状態が変化した時のみViewに通知されるようになります。

# Tips3: stateのstructにcopy()を実装しておく

Kotlinを触ったことがある人向けに説明すると、`data class`のcopyメソッドです。
触ったことがない人向けに説明すると、以下のようなメソッドです。

```Swift
let loginState = LoginState(/*省略*/)

let newLoginState = loginState.copy(loginButton: true)
// newLoginStateはloginStateのloginButtonだけtrueにし、他のフィールドは全て同じ値のstruct
```

Reduxの`Reducer`では前のstateから新しいstateを生み出すコードを書きます。
しかし、状態が複雑な場合、前の状態から新しい状態を全てについて考えていると脳がパンクします。
copyメソッドを使用することで、前の状態との差分のみ更新することができるようになり、脳のメモリが節約できます。

```Swift
func counterReducer(action: Action, state: AppState?) -> AppState {
    var state = state ?? AppState()

    switch action {
    case loginViewActions(let action):
        switch action {
            case loginButtonTapped:
                // ログインボタンがタップされたので、押せなくする
                return state.copy(loginButton: false)
        }
    }
}
```

Swiftにはcopy()を自動的に作ってくれる機構はないので、頑張って書くしかありません。
私は以下のように書きました。

```Swift

struct AppState {
    let loginButton: Bool
    let loginUserName: String?

    func copy(
        loginButton: Bool? = nil,
        loginUserName: String?? = nil
    ) {
        return AppState(
                    loginButton = loginButton ?? self.loginButton,
                    loginUserName = loginUserName ?? self.loginUserName
               )
}

```

こうすることで、copyメソッドで指定しなかった値はcopy元の値が使用されるようになります。
唯一の弊害として、明示的に`nil`を入れたい場合は以下のようにしなければなりません。
**普通に`nil`を代入するとcopy元の値が使用されてしまう！!**

```Swift
let state = AppState(/*省略*/)
state.copy(loginUserName: .some(nil))  // 明示的にloginUserNameにnilを入れてcopyする方法
```

ちょっと気をつけて使う必要がありますが、とても便利でした。ただ、copyメソッドを書くのが面倒です...


# まとめ
iOSアプリをReSwiftを活用してがっつり書いてみて学んだTips3選でした。
- Actionはenumで定義する
- RxSwiftと連携する場合はdistinctUntilChanged()を活用する
- stateにcopyメソッドを実装する

書き始める前に知っているだけで簡単に導入できると思うので、ぜひご活用ください。