MVVM + RxSwiftを案件で使いはじめて良いなと思うことは、直感的ではないあの delegate
や、手続きの多い NotificationCenter
をほとんど使わずにクラス間の状態を連携できることだと思ってます。
// viewModelのテキストの状態をviewに伝播する
viewModel.title
.bindTo(self.titleLabel)
.addDisposableTo(bag)
直感的で分かりやすい。
ただ、場合によってはviewModelの一個一個のプロパティを、データバインディングしなくても、済むケースはあると思います。
例えば、自分が今関わっている案件は UITableVIew
をRxで書いておらず標準のUIKitを使っています。
もともとtableViewには reloadData()
という便利なメソッドがあるので、それを使えばOKなケースなどです。
PublishSubjectを通知に使う
そういったデータバインディングではなく、ただ 2クラス間のイベントの伝播 だけしたい場合、PublishSubjectを通知に使うと便利です。
たとえばviewModelが、API経由でデータのfetchを完了したことをviewに通知したいとき
NotificationCenter
// 送信側(viewModel)
let nc = NotificationCenter.default
nc.post(name: Notification.Name(rawValue: "fetchNotification"), object: nil)
// 受信側(view)
nc.addObserver(self, selector: #selector(self.setup), name: Notification.Name(rawValue: "fetchNotification"), object: nil)
となるかと思いますが、文字列がキーになっているので
受信側(view)からするとどこから送信されているのかが不明です。
PublishSubjectだとこう書けます
// 送信側(viewModel)
let fetchCompleteSubject = PublishSubject<Void>()
// 受信側(view)
viewModel.fetchCompleteSubject
.asObservable()
.subscribe(onNext: { [weak self] _ in
self?.table.reloadData()
})
.addDisposableTo(bag)
補完が効くのがまず嬉しいのと、cmd + クリックで宣言元をたどれば
どこで通知が送信されているのか辿れるのが良いです。
疎結合になる
例えばview上のログインボタンを押したタイミングでviewModelがログイン処理を行う場合、ただviewModelのメソッドを呼び出すと、下記のコードになりますが
self.loginButton.rx.tap
.subscribe(onNext: { [weak self] _ in
self?.viewModel.login()
})
.addDisposableTo(bag)
viewがviewModelで何をしているのか参照しているので、
- viewModelの
loginメソッド
をprivateにできない - loginメソッドの構成を変更した場合などviewModelの変更にviewが影響を受ける
副作用があります。
これをPublishSubjectを使うとこうなります。
// view
self.loginButton.rx.tap
.bindTo(viewModel.loginButtonDidTap)
.addDisposableTo(bag)
// viewModel
let loginButtonDidTap = PublishSubject<Void>()
loginButtonDidTap
.asObservable()
.
.
viewはボタンが押されたことを通知するだけ
になるので、疎結合になり上記のような懸念が減ります。
PublishSubjectを使うだけでも、RxSwiftにはかなりのメリットがあるので、
今後もRxを案件で利用していきたいです。
参考記事
RxTodo
※ RxSwiftのアンチパターンがREADMEにまとめられており、かなり参考になります。