LoginSignup
7
5

デザインパターンと図からさくっと理解するRxSwift

Last updated at Posted at 2024-03-11

はじめに

こんにちは。
モバイルアプリエンジニア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 PublishRelayBehaviorRelayといったSubjectをラップした機能を提供している

上記以外にもRxTestやRxBlockingもありますが今回は割愛します。

Observerパターンのおさらい

次にObserverパターンについて確認します。ObserverパターンはGoFのデザインパターンの1つで、その特徴とクラス図は以下のとおりです。

  • 次のようなケースで使用されるデザインパターン
    • 1つのオブジェクトの状態が変化すると、他のオブジェクトを変更する必要があるケース
      • ただし変更すべき他のオブジェクト群は事前には未知であるか、動的に変化する
  • PublisherとSubscriber1という2つのオブジェクトによって構成される2
  • Publisherとは、
    • 通知を行うオブジェクト
      • 内部状態を持つ
        • mainState
      • 上記の状態が変化したときに、登録されているすべてのSubscriberに通知を行う
        • notifySubscribers()
    • 複数のSubscriberを登録・解除することができる
      • subscribers
      • subscribe(Subscriber s)
      • unsubscribe(Subscriber s)
  • Subscriberとは、
    • 通知を受け取るオブジェクト
      • Publisherを購読し、通知を受け取ったときに行う処理が記述されている
        • update(context)

Observerパターンから見たRxSwiftの登場人物

こちらで紹介した3つのライブラリの主要な登場人物(クラスや構造体)を、Observerパターンで出てきたPublisherとSubscriberの観点から見てみましょう。
ここでは以下の基準を用います。

  • Publisher: ObservableTypeプロトコル、もしくはObservableConvertibleTypeを実装している
  • Subscriber: ObserverTypeプロトコルを実装している

いきなりObservableTypeObservableConvertibleTypeObserverTypeというプロトコルが出てきて混乱したかもしれませんが、これから説明します。

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>
}

これらより以下のことがわかります。

  • ObservableTypesubscribeメソッドを持っている
    • これはObserverTypeプロトコルを実装しているクラスや構造体、つまりSubscriberを引数に取り、登録するためのメソッド
      • つまりPublisherのsubscribe(Subscriber s)に相当
    • 戻り値であるDisposabledisposeメソッドを呼ぶことで、購読を解除できる
      • つまりPublisherのunsubscribe(Subscriber s)に相当
  • ObservableConvertibleTypeassociatedtypeとしてElementを持っている
    • Rxでは状態変化をElementの流れとして扱うため、Elementは状態の型を表す
      • つまりPublisherのmainStateに相当

これらから、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))
    }

これより以下のことがわかります。

  • ObserverTypeonメソッドを持っている
    • これはPublisherから通知(Event<Element>)を受け取ったときに行う処理が記述される
      • つまりSubscriberのupdate(context)に相当
      • またEvent<Element>contextに相当

これらから、先ほどと同様にRxSwiftのObserverTypeはObserverパターンのSubscriberに似ていることが見て取れると思います。

【余談】ObservableTypeとObserverTypeのそれぞれのElement

少し話がそれますが、ObserverTypeonメソッドの他にonNextonCompletedonErrorといった通知の種類によって異なる処理を行うためのメソッドを持っています。

このうちonNextのパラメーターはObserverTypeElement型となっていますが、先ほどのObservableTypesubscribeメソッド内でObserver.Element == Elementという制約があるため、subscribeメソッドにObserverを渡す際、ObservableTypeElement型とObserverTypeElement型が一致するようになっています。

登場人物の関係図

それではいよいよ先ほどの観点でRxSwiftの主要な登場人物を眺めていこうと思います。
登場人物の関係図は以下のようになります。

crash_course_rxswift_0307.png

こちらの図より、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/

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

  1. クラス図では、SubscriberインタフェースとConcreteSubscriberクラスに分かれていますが、ここではこれらをまとめてSubscriberとしています。

  2. 記事や書籍によっては、PublisherをSubject、SubscriberをObserverと呼ぶこともあります。

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5