【Swift】0から始めるRxSwift。とりあえず動くものを作らせてくれ編。

# はじめに
RxSwift。難しいですよねぇ。ということで、今回はObervable?Observer?そんな小難しい言葉の説明をする前にとりあえず簡単に動くものを作らせてくれよ。という人のために記事を書きます。

# こんな人に読んでもらいたい
- RxSwiftそろそろやってみようと思ってるけどなぁという人
- とりあえずRxSwiftを使ってなにがしかのコードを書きたい人

# 説明しないこと
- ObservableやObserverなどの言葉の意味
- RxSwiftを用いたアーキテクチャに関すること

# この記事を読んで得られること
「お、RxSwift使って動くものが一応できた！」っていう感覚が味わえます。
ぜひ実際に手を動かしてコードを書いてみてください。

# 今回作るサンプル
今回作るサンプルアプリは以下のようなものです。
とてもシンプルです。
### サンプルの仕様
- 以下の２つの条件をクリアしている場合、ボタンが押せるようになる
    - 2つのTextFieldそれぞれに3文字以上入力されている
    - 2つのTextFieldに入力されている文字が同じである
- ボタンをタップするとラベルの文字が変更される

### 実際のサンプル
![Simulator Screen Recording - iPhone 14 Pro - 2023-02-18 at 15.34.31.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2644705/3346e16b-e2ee-2ff7-4b89-883b3928931e.gif)


# まずはRxを使わない実装
これは特に説明することもないかなと思うので、コードだけ貼っておきます。
```Swift
final class WithoutRxViewController: UIViewController {

    @IBOutlet private weak var textField1: UITextField!
    @IBOutlet private weak var textField2: UITextField!
    @IBOutlet private weak var label: UILabel!
    @IBOutlet private weak var button: UIButton! {
        didSet {
            button.isHidden = true
        }
    }

    @IBAction private func tappedButton(_ sender: UIButton) {
        label.text = "押されたぁぁぁ"
    }

    @IBAction func changedTextField1(_ sender: UITextField) {
        button.isHidden = !isValid()
    }

    @IBAction func changedTextField2(_ sender: UITextField) {
        button.isHidden = !isValid()
    }

    private func isValid() -> Bool {
        let text1 = textField1.text ?? ""
        let text2 = textField2.text ?? ""
        return text1.count > 2 &&  text2.count > 2 && text1 == text2
    }
}

```

# RxSwiftを用いた実装

```Swift
import UIKit
import RxSwift
import RxCocoa

final class WithRxViewController: UIViewController {

    @IBOutlet private weak var button: UIButton!
    @IBOutlet private weak var label: UILabel!
    @IBOutlet private weak var textField1: UITextField!
    @IBOutlet private weak var textField2: UITextField!

    private let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        button.rx.tap
            .subscribe( onNext: { [weak self] in
                guard let strongSelf = self else { return }
                strongSelf.label.text = "押されたぁぁぁ"
            })
            .disposed(by: disposeBag)

        Observable
            .combineLatest(textField1.rx.text.orEmpty,
                           textField2.rx.text.orEmpty)
            .subscribe(onNext: { [weak self] text1, text2 in
                guard let strongSelf = self else { return }
                strongSelf.button.isHidden = !strongSelf.isValid(text1: text1, text2: text2)
            })
            .disposed(by: disposeBag)
    }

    private func isValid(text1: String, text2: String) -> Bool {
        text1.count > 2 &&  text2.count > 2 && text1 == text2
    }
}
```

# 特徴をざっくり解説
## IBActionがないだと⁈
はい。気が付きましたか？RxSwiftを使うとタップイベント、TextFieldの文字が変更されたときのイベントをIBActionを使わずに取得することができます。これは楽ですね。


### Buttonのタップイベントを取得する
以下のように書けます。``.subscribe``の中にはボタンがタップされた時の処理を書きます。
今回は``label``の文字を変更するようにしています。


```Swift
     button.rx.tap
            .subscribe( onNext: { [weak self] in
                // タップされた時の処理を書く。
                guard let strongSelf = self else { return }
                strongSelf.label.text = "押されたぁぁぁ"
            })
            .disposed(by: disposeBag)
```

### TextFieldの文字が変化した時に文字列を取得する
例えば``textField1``の値が変化したときの文字を取得したければ以下のように書けます。
文字が変更されるたびに``.subscribe``の中は呼ばれます。
```Swift
     textField1.rx.text
            .subscribe( onNext: {
                // 文字を取得して、ここで処理できます。
                print($0)
            })
            .disposed(by: disposeBag)
```
ただし、上記の書き方だと取得できる文字はOptionalです。
もしアンラップした文字列を取得したい場合は、以下のように``.orEmpty``を使用することで実現できます。
```Swift
     textField1.rx.text.orEmpty
            .subscribe( onNext: {
                print($0)
            })
            .disposed(by: disposeBag)
```

## Observableのとこ何⁈
はいはい。ここですね。多分一番意味わからないところは。
```Swift
      Observable
            .combineLatest(textField1.rx.text.orEmpty,
                           textField2.rx.text.orEmpty)
            .subscribe(onNext: { [weak self] text1, text2 in
                guard let strongSelf = self else { return }
                strongSelf.button.isHidden = !strongSelf.isValid(text1: text1, text2: text2)
            })
            .disposed(by: disposeBag)
```
順番に説明しましょう。


先ほど以下のコードでは``textField1``の文字が変化した時に呼ばれると説明しました。

```Swift
     textField1.rx.text.orEmpty
            .subscribe( onNext: {
                // textField1の文字列が変化するたびに呼ばれる。
                print($0)
            })
            .disposed(by: disposeBag)
```
今回のサンプルでは``textField1``が変更された時に``textField1``の文字列を取得したいですが、このとき一緒に``textField2``の文字列も取得したいです。逆も然りです。
なぜ片方の``TextField``の値が変更したとき、もう片方の``TextField``の文字列も取得したいのでしょう？
これは、ボタンをタップできるかどうかの判定は両方の``TextField``の文字を使って行うから。
だから、どちらか片方の文字列に変更があったら、もう片方の文字列も取得したいんですね。

そんな時に使えるのが``.combineLatest``。
これを使うことで、``textField1``、``textField2``のどちらかが変更されたときに、両方のTextFieldの文字を一緒に取得することができます。

いかがでしょう？
では、もう一度見てみましょう。
ここのコードで``textField1``と``textField2``をまとめて、どちらかに変更があったら教えてね。というようにできます。
```Swift
     Observable
            .combineLatest(textField1.rx.text.orEmpty,
                           textField2.rx.text.orEmpty)

```
そして、以下のように``.subscribe``をつけてあげることで、どちらかが変更された時に両方の文字列を取得できます。ちなみにtextFieldは3つ4つと増やすこともできます。

あとは、取得した文字列を使用して、ボタンを表示するかしないかを判定、ボタンの``isHidden``に値を代入してあげればOKですね。なんとなくイメージできたでしょうか？

```Swift
 Observable
            .combineLatest(textField1.rx.text.orEmpty,
                           textField2.rx.text.orEmpty)
            .subscribe(onNext: { [weak self] text1, text2 in
                // text1とtext2にそれぞれの文字列が入っている
                guard let strongSelf = self else { return }
                strongSelf.button.isHidden = !strongSelf.isValid(text1: text1, text2: text2)
            })
            .disposed(by: disposeBag)
```

## viewDidLoad⁉︎
まず、このコードを見て``viewDidLoad``にしか``ボタンのタップイベント``、``文字列の変化``、``ボタンの状態の変化``に関する処理がないということにお気づきでしょうか？

画面が生成される時に１度しかこのコードは呼ばれません。
でも、文字列が変更されるたびに処理は呼ばれます。ボタンタップ時の処理が呼ばれるのは``viewDidLoad``が呼ばれた時ではありません。不思議ですね。

これは、イメージ的には``Excel``が近いと思っています。
``C4``のセルに画像のような関数を設定しておくと、``A4``、``B4``のセルに数値を入力すると、自動的に``C4``のセルの値も書き変わります。

つまりこれは、``C4``には``〇〇が起きたら、△△``してね。という処理の流れだけが登録されている状態になっているということです。
その結果、``A4``の値が変更されたら``C4``の値は変わるし、``B4``の値が変化したら``C4``も変化します。

![スクリーンショット 2023-02-18 18.40.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2644705/25d713ae-0cc6-c575-ec34-63f52e6041a7.png)

これと同じように、例えば以下のコードは「``Button``がタップされた時には△△してね」という処理の流れだけを登録しています。
だから、``viewDidLoad``で一度だけ呼べばボタンがタップされるたびに処理は行われるということですね。
```Swift
     button.rx.tap
            .subscribe( onNext: { [weak self] in
                // タップされた時の処理を書く。
                guard let strongSelf = self else { return }
                strongSelf.label.text = "押されたぁぁぁ"
            })
            .disposed(by: disposeBag)
```

textFieldについても同様に、``「値が変更したときにはバリデーションにかけて、ボタンの状態を変更してね」``という処理の流れのみを登録しておけば、あとは勝手に動いてくれるわけです。



## diposeBagってなに⁈
今回は説明しません！！
とりあえずは呪文だという認識でいいと思います。


# おまけ
一応Rxを使ったコードはこんな感じにも書き換えられるよという紹介です。
さっきのと全然ちゃうやんけーという感じのコードですね。
これも全部説明すると長くなるので今回はやめておきます。

```Swift
    override func viewDidLoad() {
        super.viewDidLoad()

        Observable
            .combineLatest(textField1.rx.text.orEmpty,
                           textField2.rx.text.orEmpty)
            .map { self.isValid(text1: $0, text2: $1) }
            .map { !$0 }
            .bind(to: button.rx.isHidden)
            .disposed(by: disposeBag)

        button.rx.tap
            .map { "押されたぁぁぁ" }
            .bind(to: label.rx.text)
            .disposed(by: disposeBag)
    }
```

# 今回のサンプルコード
もし必要であれば実際のサンプルコードも見ていってください。
https://github.com/Rin-t/SampleRx

# おわりに
RxSwiftむずかしいぃぃぃ。
おわり。