概要
- 簡単なアプリをテーマに、Callback, Delegate, RxSwift それぞれで実装したパターンを比較して、RxSwiftを学ぶ記事です
書かない
- RxSwift基礎あーだこーだ
- 公式ドキュメントが1番わかりやすいです。
ターゲットの目安
- プログラミング歴1年以上(種類問わず)
- Swift による iOS アプリの開発経験が少しだけある(3ヶ月〜1年未満)
- RxSwiftライブラリを使った開発をしたことがない
環境
- OSX High Sierra
- Xcode 9.4
- Swift 4.1
- cocoapods 1.5.3
- carthage 0.30.1
今回のテーマ
- テーマ
- カウンターアプリ
- 機能
- カウントダウン
- カウントアップ
- リセット
- アーキテクチャ
- MVVM
イメージ
実装
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番複雑(≒コード量が多く)なる