Callback, Delegateと比較して学ぶRxSwift入門　カウンターアプリ編

# 概要

- 簡単なアプリをテーマに、Callback, Delegate, RxSwift　それぞれで実装したパターンを比較して、RxSwiftを学ぶ記事です

# 書かない

- RxSwift基礎あーだこーだ
    - [公式ドキュメント](https://github.com/ReactiveX/RxSwift/blob/master/Documentation/GettingStarted.md)が１番わかりやすいです。

# ターゲットの目安

- プログラミング歴1年以上（種類問わず）
- Swift による iOS アプリの開発経験が少しだけある（３ヶ月〜１年未満）
- RxSwiftライブラリを使った開発をしたことがない

# 環境

- OSX High Sierra
- Xcode 9.4
- Swift 4.1
- cocoapods 1.5.3
- carthage 0.30.1

# 今回のテーマ

- テーマ
    - カウンターアプリ
- 機能
    - カウントダウン
    - カウントアップ
    - リセット
- アーキテクチャ
    - MVVM

# イメージ

![f.gif](https://qiita-image-store.s3.amazonaws.com/0/126441/c9716a9c-9113-bbdb-ba8e-9b155e6c15b3.gif)

# 実装

## CallBack

```swift
import UIKit

class CallBackViewController: UIViewController {

    @IBOutlet weak var countLabel: UILabel!
    
    let viewModel = CallBackViewModel()

    @IBAction func countUp(_ sender: Any) {
        viewModel.incrementCount(callback: { [weak self] count in
            self?.updateLabel(count: count)
        })
    }
    
    @IBAction func countDown(_ sender: Any) {
        viewModel.decrementCount(callback: { [weak self] count in
            self?.updateLabel(count: count)
        })
    }
    
    @IBAction func reset(_ sender: Any) {
        viewModel.resetCount(callback: { [weak self] count in
            self?.updateLabel(count: count)
        })
    }
    
    private func updateLabel(count: Int) {
        countLabel.text = "コールバックパターン: \(count)"
    }
}

class CallBackViewModel {
    private var count = 0

    func incrementCount(callback: (Int) -> ()) {
        count += 1
        callback(count)
    }

    func decrementCount(callback: (Int) -> ()) {
        count -= 1
        callback(count)
    }
    
    func resetCount(callback: (Int) -> ()) {
        count = 0
        callback(count)
    }
}

```

- 良い
    - 記述が簡単
- 悪い
    - ボタンを増やすたびにメソッドが増えていく
    - ラベルの文字変更処理も増える

## Delegate

```swift
class DelegateExmapleViewController: UIViewController {
    @IBOutlet weak var countLabel: UILabel!
    @IBOutlet weak var countButton: UIButton!

    private let presenter = DelegateExamplePresenter()

    override func viewDidLoad() {
        super.viewDidLoad()
        presenter.attachView(self)
    }

    @IBAction func countUp(_ sender: Any) {
        presenter.incrementCount()
    }

    @IBAction func countDown(_ sender: Any) {
        presenter.decrementCount()
    }

    @IBAction func resetCount(_ sender: Any) {
        presenter.resetCount()
    }

}

extension DelegateExmapleViewController: DelegateExampleView {
    func updateCount(count: Int) {
        countLabel.text = "Delegateパターン: \(count)"
    }
}

protocol DelegateExampleView {
    func updateCount(count: Int)
}

class DelegateExamplePresenter {
    private var count = 0

    private var delegateExampleView: DelegateExampleView?

    func attachView(_ view: DelegateExampleView) {
        self.delegateExampleView = view
    }
    
    func detachView() {
        self.delegateExampleView = nil
    }
    
    func incrementCount() {
        count += 1
        delegateExampleView?.updateCount(count: count)
    }

    func decrementCount() {
        count -= 1
        delegateExampleView?.updateCount(count: count)
    }

    func resetCount() {
        count = 0
        delegateExampleView?.updateCount(count: count)
    }
}

```

- 良い
    - 処理を委譲できる
    - increment, decrement, resetがデータの処理だけに集中できる
- 悪い
    - ボタンを増やすたびにメソッドが増えていく

## RxSwift/RxCocoa

```swift
import UIKit
import RxSwift
import RxCocoa

class RxViewController: UIViewController {

    @IBOutlet weak var countLabel: UILabel!
    @IBOutlet weak var countUpButton: UIButton!
    @IBOutlet weak var countDownButton: UIButton!
    @IBOutlet weak var countResetButton: UIButton!

    private let disposeBag = DisposeBag()

    let viewModel = RxViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupViewController()
        setupViewModel()
    }

    private func setupViewController() {
        countUpButton.rx.tap
            .asDriver()
            .drive(onNext: { [weak self] in
                self?.viewModel.incrementCount()
            })
            .disposed(by: disposeBag)

        countDownButton.rx.tap
            .asDriver()
            .drive(onNext: { [weak self] in
                self?.viewModel.decrementCount()
            })
            .disposed(by: disposeBag)

        countResetButton.rx.tap
            .asDriver()
            .drive(onNext: { [weak self] in
                self?.viewModel.resetCount()
            })
            .disposed(by: disposeBag)
    }

    private func setupViewModel() {
        viewModel.countRelay
            .asDriver()
            .map { return "Rxパターン: \($0)"}
            .drive(countLabel.rx.text)
            .disposed(by: disposeBag)
    }

}

class RxViewModel {
    let countRelay = BehaviorRelay<Int>(value: 0)

    func incrementCount() {
        let count = countRelay.value + 1
        countRelay.accept(count)
    }

    func decrementCount() {
        let count = countRelay.value - 1
        countRelay.accept(count)
    }

    func resetCount() {
        countRelay.accept(initialCount)
    }

}

```

- 良い
    - increment, decrement, resetがデータの処理に集中できる
    - ViewModelはViewControllerのことを考えなくても良くなる
        - 👉例: `delegate?.updateCount(count: count)` のようなデータの更新を伝えなくても良くなる
    - データとUIを `bind` することでデータ更新時UI更新！を意識しなくても良くなる
- 悪い
    - コード量が他パターンより多い　（絶対悪ってわけではないけど）
    - Outletが増える

# 総行数

|pattern|line|
|:-:|:-:|
|CallBack|50|
|Delegate|58|
|RxSwift|68|

# まとめ

- 今回のようなすごくシンプルなアプリではCallBackパターンが１番シンプルに書くことができる
    - 逆に、RxSwiftで書くと１番複雑（≒コード量が多く）なる