[WIP]CleanArchitecture + RxSwiftで見通しを良くするための気付き


Clean Architecture

  • ざっくり言うとコードを役割事に細かく分ける。
  • ファイル数が多くなるので、ファイル自動生成ツールを使うと捗る。
  • ViewControllerの肥大化を解消するためであるから、(画面数が多くても)一画面1APIくらいの場合はここまでファイルを分けても冗長なだけかもしれない。その場合はViewControllerが大体の処理を担当したとしても、そんなに肥大化しないためである。
  • フォルダの分け方は色々ある。現状は、Modelは一まとめにして1フォルダに入れる。他のファイルは、シーンごとに分けるで行こう。

Rx

  • Rxプロジェクト全体の構成
    • なるべく循環参照をしない。片側(View Controller側)が他方(Modelに近い方)をプロパティとして所有。
  • こうしたクラスが他方の側から入力を受ける場合、observableとして受け取る。
    • Observableは複数から参照された場合、Hotなら一つの処理を共有する。Coldなら、複数別々の処理が作られる。Subject系と一部のOperator(shareなど)だけがHotで、普通にObservableを作ったりそれをOperatorで受けたりした場合、Coldとなっている。同じ処理を複数走らせたくない場合が多いと思うが、その場合はshareなどのOperatorでHot変換(共通化)する。
    • UI部品に紐付けるならDriverが便利。Driverは、以下のようなOperatorの数珠つなぎをするのと同じ効果があるため。SignalもDriverとほぼ同じだが初期値がない点のみ違う。タップイベントなどViewからPresenter側へはSignal、データなどPresenter側からView側へはDriveなどが良い。
      • button.rx.tapなどのControlPropertyはエラーイベントがないことから、asSignal()でSignalに変換可能。
  .observeOn(MainScheduler.instance)       // メインスレッド上で以下を実行
  .catchErrorJustReturn(onErrorJustReturn) // エラーは無視
  .share(replay: 1, scope: .whileConnected)// Hot変換、最新の値を一つだけ返すのでUI部品の参照元データとして適当。
  • ネットワーク・API関連の部分は、成功か失敗かを返せるSingleTraitが便利。
    • 参考
    • Singleは成功か失敗かどちらかを一度だけ返す事が保証されている。onErrorはもちろんonComletedも流れるが、onNextでもonCompletedが流れるのである。なので、当たり前だが一つのSingleを使い回していると一回受け取っただけで終了してしまう。そのため、通常は他のObservableでイベントが流れてきた際に、その都度Singleを生成し、ネットワーク・API関連の処理をさせるという風に使う。
func fetchData() {
   Observable.just(Void())
      .flatMap { [unowned self] 

            self.useCase.featchData()
               .catchErrorJustReturn(ResultEntity.empty())
               //注意点として、flatMapで取り込んだObservableがerrorとなった場合、flatMap自体もerrorとなるのでそこは留意しておこう。errorが出てきては困る場合はここでcatchErrorJustReturnなどをしておく。

       } // Single<HogeData>を返すメソッドを呼ぶ
      .subscribe(onNext: { [weak self] model in 
         guard let `self` = self else { return }
         self.viewReloadData.onNext(model) // 取得したデータをViewControllerへ渡す       
       }
      .disposed(by: disposeBag)
}
  • 他方のクラスへ出力する場合は、色々やり方がある。

    • 既存のDelegate, Observerパターンの置き換えとしてRxを用いる場合は、単に他クラスのメソッドを呼び出すという手がある。
    • フルにRxを使っていく場合、他クラスへObservableを直接渡すというやり方がある。
    • それ以外に、他クラスにAnyObserverを用意しておき、それへObserableを購読させるというやり方もある。
  • テーブルビュー

  • エラーハンドリング

    • http://mudox.github.io/post/rxswift-operators---error-handling/
    • RxSwift研究読本2 エラーハンドリング編
    • 大きく分ければ、エラーが出たらそれを捉えて何かをするか、再試行するか。
    • 正常終了と異常終了
      • 正常終了の場合はobservableのonCompletedが呼ばれたのち、onDisposedが呼ばれる。一方、異常終了では、onErrorが呼ばれたのち、onDisposedが呼ばれる。なお、手動で購読破棄(diposableのdepose()メソッドを呼ぶ)した場合、onCompletedは検知されずonDisposedのみ呼ばれる。
    • エラーに応じて細かくハンドリングなどをする必要がない場合は、下記のオペレータが使える。
    • retry(n) オペレータ
      • エラーが発生してもすぐには異常終了せず、合計でn回に達するまで再試行を行なってくれる。 - catchErrorオペレータ
      • エラーを検知したら、適宜任意のobservableを返すことができる。その処理はクロージャ 内に適宜書くことができる。
    • catchErrorJustReturnオペレータ
      • エラーを検知したら、別の要素に置き換えて返す(単なるcatchErrorのラッパ)
    • また、 Driver/Signalはエラーがないため、UIレイヤのイベントを扱うには最適といえる。
    • エラー発生時にアプリも異常終了して良い場合は、これといってエラーハンドリングを行う必要はないだろう。
    • materialize: error, completedが来た場合もnextに流す(ObservableをObservable>に変換する)。errorをnextに流したい場合に使える。ただmapcatchErrorを併用する事でも同じ事はできる。
  • 購読破棄

そもそも onNext の処理が1回きりで良い場合は、take(1) を使って確実に1回で onCompolete が呼ばれるようにすることにしました。onCompolete が呼ばれれば unsubscribe の必要はないので、戻り値の Disposable は無視しても問題ありません。

onNext 内で dispose する必要がある場合は CompositeDisposable を使うようにしました。CompositeDisposable は、dispose を呼び出したときに insert してあった diposable を dispose してくれます。予め dispose を呼び出していた場合には、あとから insert した diposable は、即 dispose してくれます。これにより、onNext の実行が同期的か非同期的かは気にしなくても良くなります。

class SingletonClass {
    private let dependentObject: DependentClass

    func useTake() {
        _ = dependentObject.getObservable().take(1).subscribe(onNext: {
            // do someting.
        })
    }

    func useCompositeDisposable() {
        let compositeDisposable = CompositeDisposable()
        let disposable = dependentObject.getObservable().subscribe(onNext: {
            // do someting.
            if (conditionalExpression) {
                compositeDisposable.dispose()
            }
        })
        _ = compositeDisposable.insert(diposable)
    }
}

【RxSwift】Singleton で DisposeBag を使うことの考察

参考

[RxSwift] shareReplayをちゃんと書いてお行儀良くストリームを購読しよう
今日こそ理解するHot変換
Driver
RxSwift: share vs replay vs shareReplay
RxSwift+CleanArchitectureで構成をキレイにしよう
オブザーバーパターンから始めるRxSwift入門
RxSwiftの動作を深く理解する
RxSwiftの機能カタログ
Rxを使った設計をビジュアル化する
RxSwift入門(2) 非同期処理してみる
RxTest、RxBlockingによるテストパターン
Testing Your RxSwift Code
RxExample MVVM のその先へ(Fat ViewModel の倒し方)
[iOS] 圧倒的捗り!クリーンアーキテクチャによる開発を爆速化してくれるKuriを使ってみた
iOS開発でClean Architectureを採用した際のイイ感じのディレクトリ構成とは
RxSwiftのUITableViewとのバインディングまとめ
RxSwiftでの実装練習の記録ノート(後編:DriverパターンとAPIへの通信を伴うMVVM構成のサンプル例)

RxSwift Operators - Error Handling
SingleFlatMapSample.swift