Closed12

【Swift】RxSwift① 0->1

yoshitakayoshitaka

ストリーム

データがイベントとして連なった流れ。シーケンスとも呼ばれる。

Observable

RxSwift におけるストリームを生産する概念としてクラス Observable<T>で提供される。

オペレータ

要素からストリームを作成したり、ストリームに対して処理を行うメソッド。具体的にはストリームを作成するオペレータは of や just、処理を行うのは map や filter が該当する。

ストリームの購読

ストリームから伝搬されてくるイベントを順次処理する仕組み。

副作用

主になる作用とは別もしくはそれを原因に外部の値を変更してしまうこと。場合によっては結果的に次回以降の出力を変化させてしまう性質を持つ。

関数型プログラミング

複数の処理を関数によって組み合わせていくプログラミングスタイル。そのため、副作用を避け入力と出力の関係だけの純粋関数によってコードを書くことが推奨される。実際は正確な定義 や細かな合意というものは存在せず、複数の実装により流派が異なる。

RxSwift を利用したアプリ開発を行う場合、UIKit をサポートする RxCocoa ライブラリが利用で きます。これによって UIKit の様々なコンポーネントにリアクティブプログラミングを活かした拡 張を追加することで、関数型プログラミングとリアクティブプログラミングの良さをフルに発揮し、 ロジックの担当を分類することでコードの見通しを良くできます。

具体的には、UI を担当する「View」とビジネスロジックによるデータの変更を担当する「Model」 として分類をした上で、Model により更新されたデータを View にバインドするための処理を担当す る「ViewModel」とに分類されます。

UI ロジック

View の生成や変形、表示非表示などを担当し、さらにタッチ系イベントの設定なども行う。

プレゼンテーションロジック

ビジネスロジックの結果を UI に表示するための処理を担当する。例として入力データへのバリデート呼び出しや UI ロジックのための処理を行う。

ビジネスロジック

MVVM パターンでは UI ロジックとプレゼンテーションロジック以外の全てのロジックとも言 えるが、レイヤ化アーキテクチャの層の分類をしたければその定義はさらに細かく分類される。 本書では Model 層を細かく分類しないため、データ自身をシステムで扱いやすくした型とその処 理もしくはデータアクセスレイヤーなどが該当する。

Map と Subscribe

_ = Observable.just(10)
   .map { $0 * 2 }
   .subscribe(onNext: {
       print($0) // => 20
   })

省略せずに書くと

_ = Observable.just(10)
   .map { (arg: Int) -> Int in
       return arg * 2
   }
   .subscribe(onNext: { (arg: Int) -> Void in 
       print(arg) // => 20
   })

yoshitakayoshitaka

asObservable()メソッドは、返り値としてObservableを得ることができます。 基礎キーワードで出てきたObservable、繰り返しになりますがこれは監視可能なクラスです。

yoshitakayoshitaka

BehaviorRelayは初期値を持ち、PublishRelayは初期値を持たない

BehaviorRelayはvalueプロパティを持ち、PublishRelayはvalueプロパティを持たない

subscribeした時、BehaviorRelayは現在値を流し、PublishRelayは現在値を流さない

BehaviorRelayとPublishRelayは、.nextだけ流せる
BehaviorRelayとPublishRelayは、.nextだけ流せます。つまり、.errorや.completedが流れてこないことを保証できます。.nextイベントを流すにはacceptメソッドを使います。

yoshitakayoshitaka

ViewControllerとViewModelの役割と内部構成

ViewControllerの役割

  • UIコンポーネントをつなぐ
  • ViewModelを初期化する
  • ViewModelの出力とUIコンポーネントをバインドする
  • その他(タップを検知しキーボードを閉じる)

IBOutletなプロパティのDriverへの変換

Dirverの特性はエラーを流さない
購読直後にもし最新のイベントがあれば、そのイベントを流す
Driverとはアプリ開発者のユースケースに合わせて用意されたObservable
接続している間は副作用を共有する特性share(replay: 1, scope: .whileConnected)を行う=Hot変換

IBOutletなプロパティのSignalへの変換

タップイベントをObservableのストリームではなくSignalというストリームに変換
Driverの特性にさらにreplayされないという特性
過去のイベントを一切保持せず、その値も保持しない
購読直後にもし最新イベントがあっても、流さない
UIButtonのタップイベントに向いている

yoshitakayoshitaka

ViewControllerとViewModelの役割と内部構成

ViewControllerの役割

  • UIコンポーネントをつなぐ
  • ViewModelを初期化する
  • ViewModelの出力とUIコンポーネントをバインドする
  • その他(タップを検知しキーボードを閉じる)

ViewModelの役割

  • API用のロジックなどのイニシャライザで外部から用意できるようにする
  • イニシャライザで受け取ったObservableを処理して出力に変化

IBOutletからObservableの作成

ユーザ入力をイベントのストリームとしてViewModelへ伝えるため、IBOutletなUIコンポーネントからObservableを作成する必要がある

ViewModelの出力をViewにバインド

Observable<Bool>に対するsubscribeメソッドのonNextクロージャによりストリームの変化を取得して IBOutletにバインディングしてViewを変化させる

viewModel.viewModelでObservableが付いていたもの
    .subscribe(onNext: { [weak self] valid in
        view更新の処理(ex UIButton.isEnable)= valid
     })
     .deiposed(by: disposeBag)

もしくは
Observable<enum>の結果に対してbind(to:)メソッドを使う

viewModel.viewModelでObservableが付いていたもの
     .bind(to: UILabel.rx.enumの名称)
     .disposed(by: disposeBag)

ViewModelの出力は、viewModelでObservableが付いていたもの: Observable<enumの名称> なのでバインドされるのはObserverType<enumの名称>であることが必要

BinderとはRxSwiftによってObserverTypeプロトコルを採用する構造体であり、実際の処理内容はバインドしたい実装をBinderを初期化して実装する事で、bindメソッド時にその処理が動作する

例えば

extension Reaxtive where Base : UILabel {
    var enumの名称:  Binder<enumの名称> {
        return Binder(Base) { label, result in
             label.textColor = result.textColor
             label.text = result.description
            }
        }
    }

これはコード量を減らすために書いているだけで、毎回オリジナルを書かないといけないわけではない

yoshitakayoshitaka

なぜ ViewModelが破棄されてもイベントの変換が動作し続けるのか?

yoshitakayoshitaka

ViewModelの出力ストリームは複数ある

代表的なもの・・・

ViewModel内のObservable<OOO>を見つけたら、出力と考えて良さそう

yoshitakayoshitaka

disposeBag

まとめてObservableを処分する仕組み

disposeBagにObservableを保持させ、そのdisposeBagを破棄することでObservableをまとめて破棄できる

ViewVontrollerが破棄されるときに、そのプロパティも自動で破棄されるためにdisposeBagの仕組みが働くようになる

subscribeメソッドの戻り値 Disposableインスタンスに対してdispose()を呼び出せば手動で破棄できる

yoshitakayoshitaka
userNameOutlet.rx.text.orEmpty.asObservable()

userNameOutLet      // UITextField
    .rx                           
// Reactive<UITextField>
    .text                        
// ControlProperty<String?>
    .orEmpty                
// ControlProperty<String>
    .asObservable()    
//Observable<String>

ControlPropertyTypeのorEmptyプロパティはStringがnilの場合に空文字のStringとして変換し返してくれる

yoshitakayoshitaka

ConbineLatest

let password = PublishSubject<String>()
let repeatedPassword = PublishSubject<String>()
_ = Observable.combineLatest(password, repeatedPassword) { "\($0), \($1)"}
      .subscribe(onNext: { print("onNext: ", $0) })

combineLatest は最新の値へと切り替わるごとに動作し、合成される全てのストリーム の最新の値を使っている
複数の UITextField のイベントと共にそれらの値をストリームとして合 成するなど、複数オブジェクトの最新状態を渡す用途として利用されることが多い

yoshitakayoshitaka

Zip オペレータ

「イベントが揃ったら動作する仕組みとして割り切って使う」というのがよくある使われ方

flatMapLatest


必ずしも最新のみを購読できるわけではありません。さらに、変換元の連 続するイベントに対する変換自体は実行されており、途中で dispose されることにより結果が購読さ れないということです。

withLatestFrom

a.withLatestFrom(b)
二つの要素が揃ってイベント実行
イベントが実行されると要素は消える

このスクラップは2021/03/05にクローズされました