RxSwiftのObservableでON/OFFの状態遷移を表現

# 環境
RxSwiftとRxCocoaはCocoaPodsなどでインストールしましょう。

- XCode (12.2)
- Swift (5.3.1)
- RxSwift (6.0.0)
- RxCocoa (6.0.0)

# 実装(ON/OFF)

上層で流れてくる値に関わらずストリームに流れる値がfalse->true->falseと切り替わる関数です。
Rxオペレーターの`scan()`を使って前回の状態を参照しているところがポイントです。
`scan()`は購読するだけでは初期値を流さないので`startWith()`で初期値を流しています。

```swift
extension Observable {
    func flipflop(initialValue: Bool) -> Observable<Bool>{
        scan(initialValue) { current, _ in !current }
            .startWith(initialValue)
    }
}
```

適当なViewControllerで動くサンプルです。ボタンをタップするとON/OFFが切り替わっています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/720855/07d733b2-0059-e248-51bd-903099881163.gif" width=240>

```swift
import UIKit
import RxSwift
import RxCocoa

final class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // ラベル配置
        let label = UILabel()
        view.addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
        // ボタン配置
        let button = UIButton(type: .system)
        button.setTitle("Switch", for: .normal)
        view.addSubview(button)
        button.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 10)
        ])

        // イベント接続
        _ = button.rx.tap
            .asObservable()
            .flipflop(initialValue: false)
            .map { $0 ? "ON" : "OFF" }
            .bind(to: label.rx.text)
    }
}
```

# 実装(状態が3つ以上)
先ほどはON/OFFしか状態がない場合についての実装になりましたが、今度は3種類以上の状態を持つ場合について一般化してみましょう。
`transition`引数で次の状態を与えることで状態の遷移を表現しています。
先程実装した`flipflop()`も`state(initialValue: transition:)`の応用で置き換えられます。

```swift
extension Observable {
    func state<T>(initialValue: T,
                  transition: @escaping (T) -> T) -> Observable<T>{
        scan(initialValue) { current, _ in transition(current) }
            .startWith(initialValue)
    }

    // 上記の関数を使うとこのような実装になる
    func flipflop(initialValue: Bool) -> Observable<Bool>{
        state(initialValue: false,
              transition: !)
    }
}
```

こちらはアプリ上で動くサンプルです。`enum Hand`はじゃんけんの手を表現していて、ボタンを押すと表示されている手に勝つようグー->パー->チョキの順で表示が変わります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/720855/16468db8-f862-acaf-0013-1b99e8a9aca8.gif" width=320>


```swift

// じゃんけんの手
enum Hand {
    case rock
    case paper
    case scissors

    var name: String {
        switch self {
        case .rock: return "グー"
        case .paper: return "パー"
        case .scissors: return "チョキ"
        }
    }

    // グー<-パー<-チョキ<-グーの順に勝つ
    var defeatedBy: Hand {
        switch self {
        case .rock: return .paper
        case .paper: return .scissors
        case .scissors: return .rock
        }
    }
}


final class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // UIは同じなので省略

        // イベント接続
        _ = button.rx.tap
            .asObservable()
            .state(initialValue: Hand.rock,
                   transition: \.defeatedBy)
            .map(\.name)
            .bind(to: label.rx.text)
    }
}
```