Edited at

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


概要


  • 簡単なアプリをテーマに、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番複雑(≒コード量が多く)なる