くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

Rx(Swift)とは?

今回はリアクティブプログラミングについてまとめようと思います

RxSwiftをベースに説明しますが、概念の部分はAndroidや他のプログラミング言語でリアクティブプログラミングを行う場合でも共通すると思います

Rxとは?

Rx(Reactive X)とは、「オブザーバパターン」「イテレータパターン」「関数型プログラミング」の概念を実装している機能です

Rxを導入するメリットは、「値の変化を検知できる」「非同期の処理を簡潔に書ける」です

値の変化というのは変数の変化やUIの変化も含まれます(ボタンのタッチなども含める)

Observerパターン

Rx全般において、処理の流れの基本的な設計として用いられるものの1つに、Observerパターンと呼ばれるデザインパターンがあります

Observerパターンとは、あるオブジェクトで発生したイベントを別のオブジェクト(オブザーバー)へ通知する処理で用いられるデザインパターンの1つです

Rx関連でのイベントとは、画面上で発生するタップイベントの他に、変数の値が変わった・APIから値が取得できたなどプログラム内におけるオブジェクトの状態の変化全てを指します

RxSwiftでは、イベントストリームの中で流れて来るデータの部分をデータストリームと言われています。Observerパターンでデータストリームを扱うクラスには、次の2つのクラスがあります

  • Observable:監視可能なものを表すクラス
  • Observer:Observableからデータストリームを受け取るクラス

Observable監視可能なものを表すクラスで通知を行うことができます。一方、Observerは監視を行う側です。

Observableを「出版」、Observerを「購読」と呼ぶ場合もあります。それは次のような由来があります

出版社が事業を始め、定期的な刊行物を発行します。購読者は、出版社に購読の申し込みをすると定期的に刊行物が届けられます。 購読を解除すると、解除日以降は刊行物は届けられません。 出版者が新聞や雑誌などの定期刊行物を発行し、それを誰でも好きなタイミングで購読したり辞めたりできることと同じように考えるものです。

用語説明

Observable

Observerパターンで説明したObserverはイベントを流せるクラスで、Observableはイベントを受け取れるクラスです

RxSwiftでのイベントは次の3種類があります

イベントの種別 発生するタイミング 渡される値
next(Element) イベントが発生した際に何度でも イベント派生元の値(データストリーム)
error エラーが発生した際に一度だけ エラー情報
completed 処理が完了した際に一度だけ なし

Observableはイベントを受け取れるのですが、イベントを受け取る際にはsubscribe(_:)メソッドを利用します。次のソースコードはRxSwiftの一例です

let disposable = textField.rx.text.asObservable()
.subscribe(
  onNext: { value in
    // 通常イベント発生時の処理
  },
  onError: { error in
    // エラー発生時の処理
  },
  onCompleted: {
    // 完了時の処理
  }
)

textField.rx.text.asObservable()が Observableです。Observableが値が更新されたというイベントを受け取って、Observerはイベントに対して登録された処理を行います(今回だとonNext: { value in }など)

先程のソースコードtextField.rx.text.asObservable().subscribe()まではObservableで、onNext・onError・onCompletedの処理はObserverです(Observerはイベントを流せます)

Operator

RxにはObservableで作ったストリームを操作する機能が提供されています

ストリーム(Observable)を変換・合成・生成などの操作をオペレーターと言います

オペレーターにはmapfilterなど多くのものがあります

いくつかみていこうと思います。まずはmapです。mapは流れてきた値を変換するために使います。 下記例では入力された Int 型の値を 10倍にしています

_ = Observable.of(1,2,3)
      .map { $0 * 10 }
      .subscribe(onNext: {
          print("onNext: ", $0)
      })

// 出力結果
onNext:  10
onNext:  20
onNext:  30

次はfilterです。filterは流れてきた値で条件に合うものだけを残すために使います。 下記例では4で割り切れる数字のみを残しています

 = Observable.of(1, 2, 3, 4, 5)
 .filter { $0 % 4 == 0 }
 .subscribe(onNext: {
  print($0) // => 4
 })

そして、flatMapについてです。flatMapは今まで触れたmapやfilterとは違いがあります。それは、新たなObservableを発行するかどうかです

flatMapは元のObservableのイベント毎に、Observableに変換して、そのObservableが発行するイベントを1つに集約します

_ = Observable.of(1,2,3)
                .flatMap { number in
                    // mapとは違い新たにObservableを作成する
                    Observable.of("\(number)" + "a", "\(number)" + "b", "\(number)" + "c")
                }
                .subscribe(onNext: {
                    print("onNext: ", $0)
                })


// 出力結果(非同期なので毎回この順番という保証はありません)
onNext:  1a
onNext:  1b
onNext:  2a <- 非同期に処理が行われる
onNext:  1c
onNext:  2b
onNext:  3a
onNext:  2c
onNext:  3b
onNext:  3c

上記の例をみるとわかるようにflatMapでは新たにObservableを作成しています。マーブルダイアグラムでは次のようになります

flatMapは非同期で受け取る値の順番は毎回変わる可能性があります。APIの結果を受け取る場合にはflatMapLatestという常に一番新しいObservableを実行するオペレーターを使うようにしましょう

Subject

RxSwiftでのSubject は、ObservableとObserver両方の機能を持っています(イベントを流すこともでき、イベントを受け取ることもできる)

Observableでもsubscribeすると、onNext・onError・onCompletedの処理が行われますが、流れてくる値を受け取るための手段でObservable自らイベントを発せさせるわけではありません

Subjectは自身でイベントを流すことができます。次の例をみてください

let subject = PublishSubject<String>()
let _ = subject.subscribe(onNext: {
    print("onNext:", $0)
}, onCompleted: {
})

// イベントを流す
subject.onNext("A")
subject.onNext("B")
subject.onNext("C")
subject.onNext("D")
subject.onCompleted()

Observableクラスではsubject.onNext("A")などイベントを流すことはできませんが、Subjectだと可能です

Subjectでよく使うのは次の種類があります

種類 動作
PublishSubject キャッシュせず、来たイベントをそのまま通知する
ReplaySubject 指定した値だけキャッシュを持ち、subscribe時に直近のキャッシュしたものを通知する
BehaviorSubject 初期値を持つことができ、1つだけキャッシュを持ち、subscribe時に直近のキャッシュしたものを通知する(初期値をsubscribeで受け取れる)

HotとCold

ObservableはHotColdの2種類の性質があります

ColdなObservableはsubscribeされるまで動作せず、複数subscribeした場合にはそれぞれに対してObservableが複製されてしまいます。 なので、同じ値が複数回流れることになります。ほとんどのOpertorがColdなObservableです

Observable.of("event")
  .map { text -> String in
      print(text)
      return text
  }

// subscribeしていないので、何も出力されない

HotなObservableはsubscribeされなくても動作し、複数subscribeしているのにも関わらず、1つのObservableで同じ値がそれぞれに流れます

SubjectはHotで、mapなどのオペレーターは内部でObservableを返しますがColdなObservableを返すので、ColdなObservableです

Rxのストリームは基本的にSubscribeされた瞬間に各オペレータの動作が始まるようになっています。 ですがHot Observableをストリームの途中に挟むことで、Subscribeを実行するより前にストリームを稼働させることができます。

また、Hot Observableはストリームを分岐することができます。そして、分岐したストリームそれぞれに同一の値を渡します

最後に

大まかな説明になってしまいましたが、概念を理解することでCombineなどに活かせると思います。 また、iOS以外でのプログラミングでもリアクティブプログラミングという考え方は使うので知っておくとで他の場面で活用できる可能性もあるのでぜひ学習してみてください!

参照

github.com

open8tech.hatenablog.com

qiita.com

github.com

speakerdeck.com

github.com

qiita.com