📖
【学習備忘録】RxSwiftを使ったCounterアプリ
はじめに
MVVMのアーキテクチャを学習するにあたり、まずはRxSwiftについて学習しています。
「比較して学ぶ RxSwift4 入門」のCounterアプリのコードをもとに学習しました。
RxSwiftとは
RxSwift は「非同期イベントを受け取るための枠組み」を提供してくれるライブラリです。
・UI イベント受け取り
・Web API レスポンス受け取り
・データの変化の監視
RxSwift は ReactiveX ファミリーのひとつです。ReactiveX には、C#、Java など様々な言語のライブラリがあります。
引用:RxSwift 再入門
メリット
- 時間経過に関する処理をシンプルに書ける • コード全体が一貫する
- まとまった流れが見やすい
- 差分がわかりやすい
- 処理スレッドを変えやすい • callback を減らせる
– インデントの浅いコードに
このようなメリットがあります。
サンプルコード
完成
1. 構造体(struct)にカウントアップ・カウントダウン・カウントリセットボタンをObservable(観測可能)であることを定義する。
- ボタンを押すイベントを検知できるようにする。
struct CounterViewModelInput {
let countUpButton: Observable<Void>
let countDownButton: Observable<Void>
let countResetButton: Observable<Void>
}
2. 表示するテキストをプロトコルで定義する。
- counterTextはDriver型であることを示す。
Driverは、 - メインスレッドで通知
- shareReplayLatestWhileConnected を使った Cold-Hot 変換
- onError 通知しない
を強制することができます。
protocol CounterViewModelOutput {
var counterText: Driver<String?> { get }
}
3. InputとOutputをプロトコルで定義する。
protocol CounterViewModelType {
var outputs: CounterViewModelOutput? { get }
func setup(input: CounterViewModelInput)
}
4. ViewModelに実装
- CounterRxViewModelはCounterViewModelTypeに適合。
- functionでカウントアップ・ダウン・リセットでカウンター機能を実装。
- setupメソッドでイベントを検知した時に、何をするのか示す。
- 初期化時にリセットされるようにしておく。
- extentionでCounterRxViewModelをCounterViewModelOutputに適合させ、textの表示方法を指定し、エラー発生時にはnilが返るようにする。
class CounterRxViewModel: CounterViewModelType {
var outputs: CounterViewModelOutput?
private let countRelay = BehaviorRelay<Int>(value: 0)
private let initialCount = 0
private let disposeBag = DisposeBag()
init() {
self.outputs = self
resetCount()
}
func setup(input: CounterViewModelInput) {
input.countUpButton
.subscribe(onNext: { [weak self] in
self?.incrementCount()
})
.disposed(by: disposeBag)
input.countDownButton
.subscribe(onNext: { [weak self] in
self?.decrementCount()
})
.disposed(by: disposeBag)
input.countResetButton
.subscribe(onNext: { [weak self] in
self?.resetCount()
})
.disposed(by: disposeBag)
}
private func incrementCount() {
let count = countRelay.value + 1
countRelay.accept(count)
}
private func decrementCount() {
let count = countRelay.value - 1
countRelay.accept(count)
}
private func resetCount() {
countRelay.accept(initialCount)
}
}
extension CounterRxViewModel: CounterViewModelOutput {
var counterText: Driver<String?> {
return countRelay
.map { "Rxパターン:\($0)" }
.asDriver(onErrorJustReturn: nil)
}
}
5. ViewControllerを実装
- setUpViewModelのメソッド内に、inputにそれぞれのボタンタップ処理を指定し、outputsにtextを指定する
- ViewDidLoad()でseyupViewModelを呼び出す。
class ViewController: UIViewController {
@IBOutlet weak var countLabel: UILabel!
@IBOutlet weak var countUpBotton: UIButton!
@IBOutlet weak var countDownButton: UIButton!
@IBOutlet weak var countResetButton: UIButton!
private let disposeBag = DisposeBag()
private var viewModel: CounterRxViewModel!
override func viewDidLoad() {
super.viewDidLoad()
setupViewModel()
}
private func setupViewModel() {
viewModel = CounterRxViewModel()
let input = CounterViewModelInput(
countUpButton: countUpBotton.rx.tap.asObservable(),
countDownButton: countDownButton.rx.tap.asObservable(),
countResetButton: countResetButton.rx.tap.asObservable()
)
viewModel.setup(input: input)
viewModel.outputs?.counterText
.drive(countLabel.rx.text)
.disposed(by: disposeBag)
}
}
GitHub
参考にしたもの
1.比較して学ぶRxSwift入門
2.RxSwift 再入門
3.【RxSwift】BehaviorRelayとPublishRelayについてまとめてみた
4.RxCocoaが提供するDriverって何?
5.ReactiveX/RxSwift
さいごに
実際に手を動かしてみるとコードを読むだけではわからないところが理解できました。
また、調べながらまとめていくとより理解が深まりました。
しかし、よくわかっていないところも多く、個人開発でもRxSwiftを使って(Combineも使いたいなー...)理解を深めていきたいと思います。
間違いや認識違いがあれば指摘いただければ幸いです。
Discussion