LoginSignup
6
1

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-08-08

概要

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

書かない

ターゲットの目安

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

環境

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

今回のテーマ

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

イメージ

f.gif

実装

CallBack

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

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

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パターンが1番シンプルに書くことができる
    • 逆に、RxSwiftで書くと1番複雑(≒コード量が多く)なる
6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1