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

## はじめに

こんにちは。
モバイルアプリエンジニア2年目として、iOSアプリやAndroidアプリの開発を行っています。

最近の開発の中で、ReactiveX（以下、Rx）にはじめて出会ったのですが、このRxなかなかとっつきにくく理解するのにかなり苦労していました。そこでObserverパターンの観点から、Rxの登場人物の役割や関係を図で整理してみるとイメージがかなりつかみやすかったので、記事としてまとめてみました。

最近のトレンドを考えると新規のプロジェクトでRxを使用することは少ないかもしれませんが、自分のように何らかの理由でRxを理解する必要が出てきた場合に、この記事が少しでも役に立てば幸いです。

:::note warn
本記事はSwiftを対象としてます。
具体的なライブラリとバージョンは以下のとおりです。

- RxSwift v6.x
- RxCocoa v6.x
:::

## この記事の対象者

- Swiftの基本的な文法をある程度理解している人
- GoFのObserverパターンについてある程度知っている人
  - 本記事でも簡単に触れますが、そうでない方は[こちら](https://refactoring.guru/design-patterns/observer)の記事を一読していただくと良いかもしれません。非常にわかりやすく個人的にオススメです。

## Rxのおさらい

まずRxの基本事項について確認します。[公式](https://reactivex.io/)ではRxについて以下のように説明されています

- 非同期およびイベントベースのプログラムを構築するためのライブラリ
- ObserverパターンやIteratorパターン、関数型プログラミングを組み合わせている
  - データ・イベントのシーケンスのサポートするために、Observerパターンを拡張
  - 宣言的にシーケンスを組み合わせるための演算子を追加
- 低レベルのスレッド管理や同期処理、スレッドセーフなどの問題を抽象化

上記より、Rxにはさまざまなパターンやパラダイムが含まれていることがわかりますね。これがRxの理解を難しくしている要因かもしれません。
ただObserverパターンが基礎となっているようですので、理解の出発点として活用できそうです。

ここでObserverパターンを見る前に、SwiftにおけるRxライブラリ群について確認します。

### SwiftにおけるRxライブラリ

Swiftで用いられる主なRxライブラリは以下の3つです。今回はこれらを中心にRxの理解を進めていきます。

|ライブラリ名|概要|
|---|---|
|[RxSwift](https://github.com/ReactiveX/RxSwift)|RxSwiftのコアとなるライブラリ <br> ReactiveXで定義されたRxの標準機能を提供している|
|[RxCocoa](https://github.com/ReactiveX/RxSwift/tree/main/RxCocoa)|Rxを使用してiOSアプリやmacOSアプリなどを開発するための機能を提供している|
|[RxRelay](https://github.com/ReactiveX/RxSwift/tree/main/RxRelay)|`PublishRelay`、`BehaviorRelay`といった`Subject`をラップした機能を提供している|

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

## Observerパターンのおさらい

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

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

```mermaid
classDiagram
  direction LR
  class Publisher {
    -subscribers: List~Subscriber~
    -mainState
    +subscribe(Subscriber s)
    +unsubscribe(Subscriber s)
    +notifySubscribers()
    +mainBusinessLogic()
  }
  class Subscriber {
    +update(context)
  }
  class ConcreteSubscribers {
    +update(context)
  }
  <<Interface>> Subscriber 
  Publisher o--> Subscriber 
  Subscriber <|.. ConcreteSubscribers 
  
  class Client {
  }
  Client --> Publisher 
  Client ..> ConcreteSubscribers
```

[^1]: クラス図では、SubscriberインタフェースとConcreteSubscriberクラスに分かれていますが、ここではこれらをまとめてSubscriberとしています。
[^2]: 記事や書籍によっては、PublisherをSubject、SubscriberをObserverと呼ぶこともあります。

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

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

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

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

#### `ObservableType`プロトコルとPublisher

早速RxSwiftで実装されている`ObservableType`のコードの一部を見てみます。

```swift
// コメントは省略
public protocol ObservableType: ObservableConvertibleType {
    func subscribe<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element
}
```

また、以下は`ObservableConvertibleType`のコードです。

```swift
// コメントは省略
public protocol ObservableConvertibleType {
    associatedtype Element

    func asObservable() -> Observable<Element>
}
```

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

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

これらから、RxSwiftの`ObservableType`はObserverパターンのPublisherに似ていることが見て取れると思います。

:::note warn
今回`ObservableConvertibleType`のみ実装しているものもPublisherとして扱っています。
これは後述する`Driver`のように、内部に`ObservableType`を実装したデータをプロパティとして持つものが多く、`ObservableType`の役割をそのプロパティに委譲しており、実質的にPublisherの振る舞いを行っているとみなせるためです。
:::

### `ObserverType`プロトコルとSubscriber

次にRxSwiftで実装されている`ObserverType`のコードの一部を見てみます。

```swift
// コメントは省略
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`に相当

これらから、先ほどと同様に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の主要な登場人物を眺めていこうと思います。
登場人物の関係図は以下のようになります。

![crash_course_rxswift_0307.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/646568/8bc2f3e0-dba3-da05-565b-4bb74a6e1b65.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>