はじめに
こんにちは。
モバイルアプリエンジニア2年目として、iOSアプリやAndroidアプリの開発を行っています。
最近の開発の中で、ReactiveX(以下、Rx)にはじめて出会ったのですが、このRxなかなかとっつきにくく理解するのにかなり苦労していました。そこでObserverパターンの観点から、Rxの登場人物の役割や関係を図で整理してみるとイメージがかなりつかみやすかったので、記事としてまとめてみました。
最近のトレンドを考えると新規のプロジェクトでRxを使用することは少ないかもしれませんが、自分のように何らかの理由でRxを理解する必要が出てきた場合に、この記事が少しでも役に立てば幸いです。
本記事はSwiftを対象としてます。
具体的なライブラリとバージョンは以下のとおりです。
- RxSwift v6.x
- RxCocoa v6.x
この記事の対象者
- Swiftの基本的な文法をある程度理解している人
- GoFのObserverパターンについてある程度知っている人
- 本記事でも簡単に触れますが、そうでない方はこちらの記事を一読していただくと良いかもしれません。非常にわかりやすく個人的にオススメです。
Rxのおさらい
まずRxの基本事項について確認します。公式ではRxについて以下のように説明されています
- 非同期およびイベントベースのプログラムを構築するためのライブラリ
- ObserverパターンやIteratorパターン、関数型プログラミングを組み合わせている
- データ・イベントのシーケンスのサポートするために、Observerパターンを拡張
- 宣言的にシーケンスを組み合わせるための演算子を追加
- 低レベルのスレッド管理や同期処理、スレッドセーフなどの問題を抽象化
上記より、Rxにはさまざまなパターンやパラダイムが含まれていることがわかりますね。これがRxの理解を難しくしている要因かもしれません。
ただObserverパターンが基礎となっているようですので、理解の出発点として活用できそうです。
ここでObserverパターンを見る前に、SwiftにおけるRxライブラリ群について確認します。
SwiftにおけるRxライブラリ
Swiftで用いられる主なRxライブラリは以下の3つです。今回はこれらを中心にRxの理解を進めていきます。
ライブラリ名 | 概要 |
---|---|
RxSwift | RxSwiftのコアとなるライブラリ ReactiveXで定義されたRxの標準機能を提供している |
RxCocoa | Rxを使用してiOSアプリやmacOSアプリなどを開発するための機能を提供している |
RxRelay |
PublishRelay 、BehaviorRelay といったSubject をラップした機能を提供している |
上記以外にもRxTestやRxBlockingもありますが今回は割愛します。
Observerパターンのおさらい
次にObserverパターンについて確認します。ObserverパターンはGoFのデザインパターンの1つで、その特徴とクラス図は以下のとおりです。
- 次のようなケースで使用されるデザインパターン
- 1つのオブジェクトの状態が変化すると、他のオブジェクトを変更する必要があるケース
- ただし変更すべき他のオブジェクト群は事前には未知であるか、動的に変化する
- 1つのオブジェクトの状態が変化すると、他のオブジェクトを変更する必要があるケース
- PublisherとSubscriber1という2つのオブジェクトによって構成される2
- Publisherとは、
-
通知を行うオブジェクト
- 内部状態を持つ
mainState
- 上記の状態が変化したときに、登録されているすべてのSubscriberに通知を行う
notifySubscribers()
- 内部状態を持つ
- 複数のSubscriberを登録・解除することができる
subscribers
subscribe(Subscriber s)
unsubscribe(Subscriber s)
-
通知を行うオブジェクト
- Subscriberとは、
-
通知を受け取るオブジェクト
- Publisherを購読し、通知を受け取ったときに行う処理が記述されている
update(context)
- Publisherを購読し、通知を受け取ったときに行う処理が記述されている
-
通知を受け取るオブジェクト
Observerパターンから見たRxSwiftの登場人物
こちらで紹介した3つのライブラリの主要な登場人物(クラスや構造体)を、Observerパターンで出てきたPublisherとSubscriberの観点から見てみましょう。
ここでは以下の基準を用います。
- Publisher:
ObservableType
プロトコル、もしくはObservableConvertibleType
を実装している - Subscriber:
ObserverType
プロトコルを実装している
いきなりObservableType
やObservableConvertibleType
、ObserverType
というプロトコルが出てきて混乱したかもしれませんが、これから説明します。
ObservableType
プロトコルとPublisher
早速RxSwiftで実装されているObservableType
のコードの一部を見てみます。
// コメントは省略
public protocol ObservableType: ObservableConvertibleType {
func subscribe<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element
}
また、以下はObservableConvertibleType
のコードです。
// コメントは省略
public protocol ObservableConvertibleType {
associatedtype Element
func asObservable() -> Observable<Element>
}
これらより以下のことがわかります。
-
ObservableType
はsubscribe
メソッドを持っている- これは
ObserverType
プロトコルを実装しているクラスや構造体、つまりSubscriberを引数に取り、登録するためのメソッド- つまりPublisherの
subscribe(Subscriber s)
に相当
- つまりPublisherの
- 戻り値である
Disposable
のdispose
メソッドを呼ぶことで、購読を解除できる- つまりPublisherの
unsubscribe(Subscriber s)
に相当
- つまりPublisherの
- これは
-
ObservableConvertibleType
はassociatedtype
としてElement
を持っている- Rxでは状態変化を
Element
の流れとして扱うため、Element
は状態の型を表す- つまりPublisherの
mainState
に相当
- つまりPublisherの
- Rxでは状態変化を
これらから、RxSwiftのObservableType
はObserverパターンのPublisherに似ていることが見て取れると思います。
今回ObservableConvertibleType
のみ実装しているものもPublisherとして扱っています。
これは後述するDriver
のように、内部にObservableType
を実装したデータをプロパティとして持つものが多く、ObservableType
の役割をそのプロパティに委譲しており、実質的にPublisherの振る舞いを行っているとみなせるためです。
ObserverType
プロトコルとSubscriber
次にRxSwiftで実装されているObserverType
のコードの一部を見てみます。
// コメントは省略
public protocol ObserverType {
associatedtype Element
func on(_ event: Event<Element>)
}
extension ObserverType {
public func onNext(_ element: Element) {
self.on(.next(element))
}
public func onCompleted() {
self.on(.completed)
}
public func onError(_ error: Swift.Error) {
self.on(.error(error))
}
これより以下のことがわかります。
-
ObserverType
はon
メソッドを持っている- これはPublisherから通知(
Event<Element>
)を受け取ったときに行う処理が記述される- つまりSubscriberの
update(context)
に相当 - また
Event<Element>
はcontext
に相当
- つまりSubscriberの
- これはPublisherから通知(
これらから、先ほどと同様にRxSwiftのObserverType
はObserverパターンのSubscriberに似ていることが見て取れると思います。
【余談】ObservableTypeとObserverTypeのそれぞれのElement
少し話がそれますが、ObserverType
はon
メソッドの他にonNext
、onCompleted
、onError
といった通知の種類によって異なる処理を行うためのメソッドを持っています。
このうちonNext
のパラメーターはObserverType
のElement
型となっていますが、先ほどのObservableType
のsubscribe
メソッド内でObserver.Element == Element
という制約があるため、subscribe
メソッドにObserverを渡す際、ObservableType
のElement
型とObserverType
のElement
型が一致するようになっています。
登場人物の関係図
それではいよいよ先ほどの観点でRxSwiftの主要な登場人物を眺めていこうと思います。
登場人物の関係図は以下のようになります。
こちらの図より、SubscriberよりもPublisherの方が多くのクラスや構造体が用意されていたり、PublishSubject
のようなSubscriberとPublisherのハイブリットなものも存在していることがわかります。
次にそれぞれの登場人物について詳しく見ていきましょう。
Publisher系の登場人物
Observable
Observable
とはRxSwiftでPublisherを実装する基本的なクラスです。
以下のケースで使用されます
- 非同期操作が必要な場合(たとえば、ネットワークリクエストや時間のかかるデータ処理など)
- 複数のデータソースを組み合わせて1つのデータストリームにしたい場合
- データの変更を監視し、変更があるたびにUIの更新が求められる場面
PublishRelay
後述するPublishSubject
をラップしたクラスです。以下のような特徴があります。
-
next
イベントのみを通知する -
accept
メソッドによって、自身を購読しているSubscriberにnext
イベント(つまり状態変更)を通知する - 初期状態を持たない
- 新しくSubscriberを登録した直後は、Subscriberに対して何も通知しない
- 後述の
BehaviorRelay
では、その時点の状態をnext
イベントとして登録したSubscriberに通知する
- 後述の
BehaviorRelay
後述するBehaviorSubject
をラップしたクラスです。以下のような特徴があります。
-
next
イベントのみを通知する -
accept
メソッドによって、自身を購読しているSubscriberにnext
イベント(つまり状態変更)を通知する - 初期状態を持つ
- 新しくSubscriberを登録した直後、その時点の状態を
next
イベントとして登録したSubscriberに通知する
これは、APIからフェッチした最新のデータ(ユーザー情報など)をキャッシュとして保持しておきたい場合によく用いられています。
Driver
Driver
とは、Observable
をラップしたクラスで、特定の制約を持つことでUIの更新に特化しているPublisherです。
具体的には、以下の特徴を持ちます。
- メインスレッドでのみ実行
- これにより、UIの更新がメインスレッド以外で行われることによる問題を防ぐことができる
- エラーを発生させない
- このため、エラーハンドリングのコードを書かなくてよい
- 複数のSubscriberに同じイベントを共有することができる
- 具体的には、Subscriberが登録されている間に新しいSubscriberを追加直後に最新の状態(1つの要素)を通知
- 購読者がいないときにはリソースを解放するため、効率よくメモリ管理を行える
またDriver
は他のPublisherと異なり、drive
メソッドによってSubscriberを登録します。
Subscriber系の登場人物
AnyObserver
AnyObserver
とはObserverType
の型消去を提供するラッパーです。
これは、具体的なObserverの型を隠蔽することで、型安全性を維持しつつも柔軟性を提供できるという特徴があります。
Binder
Binder
とは、以下の制約を持つことでUIの更新に特化しているSubscriberです。
- メインスレッドでのみ実行
- これにより、UIの更新がメインスレッド以外で行われることによる問題を防ぐことができる
-
error
イベントをバインドしない- これにより、エラーハンドリングのコードを書かなくてよい
ハイブリット(PublisherかつSubscriber)系の登場人物
ここで登場するSubject
は、PublisherとSubscriberの両方として機能するため、ブリッジまたはプロキシとして用いられることが多いです。
たとえば1つのSubject
に対して複数のSubscriberを登録し、そのSubject
自身を別のPublisherに登録することで、簡単にマルチキャストを実現できます。
PublishSubject
以下のような特徴を持つSubject
です。
- 初期状態を持たない
- 新しくSubscriberを登録した直後は、Subscriberに対して何も通知しない
- 登録後、
PublishSubject
の状態が変化したときに、はじめて登録したSubscriberに通知する
- 登録後、
BehaviorSubject
以下のような特徴を持つSubject
です。
- 初期状態を持つ
- 新しくSubscriberを登録した直後、その時点の状態を通知する
参考記事
https://reactivex.io/intro.html
https://reactivex.io/documentation/subject.html
https://refactoring.guru/design-patterns/observer
https://github.com/ReactiveX/RxSwift/blob/main/Documentation/Traits.md#driver