はじめに
こんにちは。
モバイルアプリエンジニア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を登録・解除することができる
subscriberssubscribe(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
