【Swift × RxSwift】 RxSwift を使ってコード実装してみた！

# 目的
RxSwift の使い方をまとめること
単語の意味も詳しくまとめたい
MVVM で開発を行えるようになること


# 流れ
1: SPM(Swift Package Manager)を使って RxSwift をインストール
2: ViewController のみで簡単に実装
3: ViewController と ViewModel で実装
4: TextField に入力された文字を Label として表示する
5: 単語集



# SPM(Swift Package Manager)を使って RxSwift をインストール
[こちらを参考](https://qiita.com/Soccerboy_Hamada/items/037f80c95acc9ae9949e)


# ViewController のみで簡単に実装

```ViewController.swift
import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {

    private let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .blue
        
        // MARK: RxSwift
        let helloWorldSubject = PublishSubject<String>()   // ①: Subject の定義
        
        helloWorldSubject   // ②: Subject を購読
            // ③: 流れてきたデータを表示
            .subscribe(onNext: { message in
                print("onNext: \(message)")
            })
            .disposed(by: disposeBag)
        
        // ④: イベントを流す
        helloWorldSubject.onNext("Hello World!_1")
        helloWorldSubject.onNext("Hello World!_2")
        helloWorldSubject.onNext("Hello World!_3")
        helloWorldSubject.onCompleted()   // 終了
    }
}
```


# ViewController と ViewModel で実装
```ViewController.swift
import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {

    private let disposeBag = DisposeBag()
    private var viewModel: ViewModel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .blue
        
        
        viewModel = ViewModel()
        
        // ■ 受け取りに使用する
        let helloWorldObservable = viewModel.helloWorldObservable   // ①: Subject の定義
        
        // ■ 受け取る, 受け取りを"宣言"する
        helloWorldObservable   // ②: Subject を購読
            // ③: 流れてきたデータを表示
            .subscribe(onNext: { [weak self] message in
                print("massage: \(message)")
            })
            .disposed(by: disposeBag)
        
        // ■ 流す
        viewModel.updateItem()   // ④: イベントを流す
    }

}
```

```ViewModel.swift
import RxSwift
import RxCocoa

class ViewModel {
    
    // ■ 受け取りに使用
    // Observable: イベントを検知するためのクラス
    var helloWorldObservable: Observable<String> {
        return helloWorldSubject.asObservable()
    }
    
    // ■ 流すために使用
    public let helloWorldSubject = PublishSubject<String>()
    
    func updateItem() {
        helloWorldSubject.onNext("Hello World!_1")
        helloWorldSubject.onNext("Hello World!_2")
        helloWorldSubject.onNext("Hello World!_3")
        helloWorldSubject.onCompleted()   // 終了
    }
}
```


# TextField に入力された文字を Label として表示する
bind と Subscribe の2種類で実装している

### 結果
<img width = 300 src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764557/539b3a19-bb2e-36f3-1053-fe99fa3e9300.gif">

### プログラム

<details>
<summary>プログラム</summary>

```ViewController.swift
import UIKit
import RxSwift
import RxCocoa
import SnapKit   // レイアウト

class ViewController: UIViewController {
    
    // MARK: Cells
    let sampleTextField: UITextField = {
        let textField = UITextField()
        textField.text = ""
        textField.placeholder = "文字を入力してください"
        textField.backgroundColor = UIColor.yellow
        return textField
    }()
    
    var bindTextLabel: UILabel = {
        let label = UILabel()
        label.text = ""
        label.backgroundColor = .green
        label.textColor = .black
        return label
    }()
    
    var subscribeTextLabel: UILabel = {
        let label = UILabel()
        label.text = ""
        label.backgroundColor = .red
        label.textColor = .black
        return label
    }()

    private let disposeBag = DisposeBag()
    private var viewModel: ViewModel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupLayout()
        setupBinding()
        
        viewModel = ViewModel()
        
        // ■ 受け取りに使用する
        let helloWorldObservable = viewModel.helloWorldObservable   // ①: Subject の定義
        
        // ■ 受け取る, 受け取りを"宣言"する
        helloWorldObservable   // ②: Subject を購読
            // ③: 流れてきたデータを表示
            .subscribe(onNext: { [weak self] message in
                print("massage: \(message)")
            })
            .disposed(by: disposeBag)
        
        // ■ 流す
        viewModel.updateItem()   // ④: イベントを流す
    }
    
    // SnapKit を使って画面を描画
    private func setupLayout() {
        view.backgroundColor = .blue
        view.addSubview(sampleTextField)
        view.addSubview(bindTextLabel)
        view.addSubview(subscribeTextLabel)
        
        sampleTextField.snp.makeConstraints { make in
            make.width.equalTo(300)
            make.height.equalTo(100)
            make.top.equalTo(view.snp.top).offset(100)
            make.centerX.equalToSuperview()
        }
        
        bindTextLabel.snp.makeConstraints { make in
            make.width.equalTo(300)
            make.height.equalTo(100)
            make.top.equalTo(sampleTextField.snp.bottom).offset(20)
            make.centerX.equalToSuperview()
        }
        
        subscribeTextLabel.snp.makeConstraints { make in
            make.width.equalTo(300)
            make.height.equalTo(100)
            make.top.equalTo(bindTextLabel.snp.bottom).offset(20)
            make.centerX.equalToSuperview()
        }
    }
    
    private func setupBinding() {
        // bind を使用
        sampleTextField.rx.text
            //     ↓変換前   ↓変換後
            .map { text -> String? in
                guard let text = text else { return nil }
                return "あと\(10 - text.count)文字です"
            }
            .bind(to: bindTextLabel.rx.text)
            .disposed(by: disposeBag)
        
        // subscribe を使用
        sampleTextField.rx.text
            .subscribe(onNext: { [weak self] text in
                self?.subscribeTextLabel.text = text
            })
            .disposed(by: disposeBag)
    }

}
```

```ViewModel.swift
import RxSwift
import RxCocoa

class ViewModel {
    // 変更なし
}
```
</details>


**👇重要な部分を抜粋**

```ViewController.swift
// bind を使用, 黄緑
sampleTextField.rx.text
    .bind(to: bindTextLabel.rx.text)
    .disposed(by: disposeBag)
        
// subscribe を使用, 　赤
sampleTextField.rx.text
    .subscribe(onNext: { [weak self] text in
        self?.subscribeTextLabel.text = text
    })
    .disposed(by: disposeBag)
```


# Operator を使ってデータの加工



```ViewController.swift
sampleTextField.rx.text
    //     ↓変換前  ↓変換後
    .map { text -> String? in
       guard let text = text else { return nil }
       return "あと\(10 - text.count)文字"
    }
    .bind(to: bindTextLabel.rx.text)
    .disposed(by: disposeBag)
```











# 単語集

`RxCocoa`: UIKitやAppKit向けのextensionがswiftで集められたもの[1]<br>
`DisposeBag`: イイ感じに購読を破棄して、メモリーリークを回避するための仕組み[2]<br>
`Observable`: 観測可能なものを表現しイベントを検知するためのクラス, イベント発生元, イベントを検知するためのクラス[2]<br>
`Observaer`: イベント処理[2]<br>
`PublishSubject`: イベント検知に加えて発生(イベント内容/エラー/完了)を行うクラス[2]<br>
`Subject`: 「通信処理やDB処理等」エラーが発生したときにその内容によって処理を分岐させたい[2]<br>
`Relay`: UIに値をBindする[2]<br>
`subscribe`: Observableから通知を受け取ったらどのような振る舞いをするかを定義する[3]<br>
`onNext`: 送られてきたイベントを受け取る<br>
`bind`: 値を代入すること, 単方向データバインディング<br>
`Operator`: Obserbableに対してイベントの値を加工して、新しく** を作成する仕組み[2]<br>
<details>
<summary>Operator の種類とメソッドと役割[2],[4],[5]</summary>

|  種類  |  メソッド名    |  役割  |
| ----  | ----        | ---- |
|  変換  |  map        |  データ(nilを含む)を変換して通す  |
|       |  flatMap    |  データを1次元にして返す  |
|       |  reduce     |  全ての要素の累積を求める  |
|       |  scan       |  reduce の途中結果をイベント発行できるもの  |
|       |  debounce   |  指定時間イベントが発生しなかった場合、最後に流されたイベントを流す  |
|  絞り込み  |  filter    |  指定した条件を満たす要素だけを含む配列を返す  |
|          |  take      |  指定時間の間だけイベントを通知して　onCompleted する  |
|          |  skip      |  指定時間の間はイベントを無視する  |
|          |  distinct  |  重複イベントを除外する  |
|  組み合わせ  |  zip            |  複数の Observable を組み合わせる(異なる型同士 可)  |
|            |  merge          |  複数の Observable を組み合わせる(異なる型同士 不可)  |
|            |  combineLatest  |  複数の Observable の最新値を組み合わせる  |
|            |  sample         |  引数に Observable のイベントを渡し、イベント発生時に元の Observable の最新イベントを通知  |
|            |  concat         |  複数の Observable のイベントを順番に組み合わせる(異なる方同士 不可)  |

</details>

`Driver`: UIに特化したObservable, 「エラーを流さない・メインスレッドの保証」という利点がある。UIViewController でのバインディングに推奨されている (swiftにはUIの描画や更新はメインスレッドで行わなければならないというルール), Driver型に変換する ようなイメージで使用する。<br>


# 参考資料
[1]. RxSwiftでUIKitを扱うにはRxCocoaが必要

https://swiftlife.hatenablog.jp/entry/2016/02/15/094408


[2]. 比較して学ぶRxSwift入門 (技術の泉シリーズ（NextPublishing）) Kindle版 ← ⭐️ 筆者おすすめ

https://www.amazon.co.jp/%E6%AF%94%E8%BC%83%E3%81%97%E3%81%A6%E5%AD%A6%E3%81%B6RxSwift%E5%85%A5%E9%96%80-%E6%8A%80%E8%A1%93%E3%81%AE%E6%B3%89%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA%EF%BC%88NextPublishing%EF%BC%89-%E9%AB%99%E6%A9%8B-%E5%87%8C-ebook/dp/B07LGMNXS3


[3]. 入門！RxSwift ← ⭐️ 筆者おすすめ

https://open8tech.hatenablog.com/entry/2019/04/03/111100


[4]. 【Swift】シーケンスの高階関数まとめ

https://dasuko.hatenadiary.jp/entry/2021/02/25/190000#filter


[5]. イメージで理解するSwiftの高階関数（filter, map, reduce, compactMap, flatMap） - Qiita

https://qiita.com/shtnkgm/items/eaad3c6ab368463de8e3#reduce


[6]. RxCocoa 4 の Signal と Relay のまとめ - mercari engineering

https://engineering.mercari.com/blog/entry/2017-12-04-103247/


[7]. RxCocoaが提供するDriverって何？ - Qiita

https://qiita.com/k5n/items/44ef2ab400f47fb66731