🐉

RXの世界を学んでみた

に公開

RXSwiftを使って学習してみる

リアクティブプログラミングの概念が必要なプロジェクトに入った。「ビーヘービアー」とか聞き慣れない単語が出てきたので勉強してみる。

参考になったサイト

https://qiita.com/kazuma___/items/c803b4a29647005881f7

学習に使ったサンプル

サンプルというけど難しそう💦

まずは単語から理解したい

RxSwift 重要用語集

用語 日本語訳 RxSwiftでの概念
weak self 弱参照 クロージャ内でのメモリリーク防止のための参照方法。self(インスタンス自身)への強参照を避けることで循環参照を防ぐ
subscribe 購読する Observableが発行するイベントを受け取るための操作。イベントストリームの終点として機能する
Observable 観察可能な イベントを発行するストリームを表現する型。時間とともに値を発行できる
Behavior 振る舞い 最新の値を保持し、新規購読者に対して即座にその値を提供する特性を持つObservable
Publish 公開する 購読開始後のイベントのみを提供する特性を持つObservable
Replay 再生する 指定した数の過去のイベントを保持し、新規購読者に提供する特性を持つObservable

詳細解説

今回は説明だけにしておこうかなと。興味ある人はサンプル動かしてみてください。今はRXSwiftを使うことはあまりないと思われますが。

weak self

// 悪い例(メモリリークの可能性)
observable.subscribe(onNext: { value in
    self.updateUI(value)
})

// 良い例
observable.subscribe(onNext: { [weak self] value in
    self?.updateUI(value)
})

Observable の種類と特徴

1. PublishSubject

let subject = PublishSubject<Int>()

特徴:

  • 購読開始後に発生したイベントのみを受け取ります
  • 初期値を持ちません
  • Hot Observable(購読開始のタイミングが重要)

使用場面:

  • リアルタイムのイベント通知(ボタンタップなど)
  • ライブデータの配信(株価の更新など)
  • 過去のデータが不要な場合

実装例:

// ボタンタップのイベント処理
let buttonTapSubject = PublishSubject<Void>()

button.rx.tap
    .bind(to: buttonTapSubject)
    .disposed(by: disposeBag)

buttonTapSubject
    .subscribe(onNext: { 
        print("ボタンがタップされました")
    })
    .disposed(by: disposeBag)

2. BehaviorSubject

let subject = BehaviorSubject(value: 0)

特徴:

  • 必ず初期値を持ちます
  • 購読開始時に最新値を受け取れます
  • 状態を保持します

使用場面:

  • UIの状態管理(テキストフィールドの現在値など)
  • 設定値の管理
  • 常に値を持っている必要があるデータ

実装例:

// テキストフィールドの値管理
let textSubject = BehaviorSubject(value: "")

textField.rx.text
    .bind(to: textSubject)
    .disposed(by: disposeBag)

textSubject
    .subscribe(onNext: { text in
        print("現在のテキスト: \(text)")
    })
    .disposed(by: disposeBag)

3. ReplaySubject

let subject = ReplaySubject<Int>.create(bufferSize: 2)

特徴:

  • 指定したバッファサイズ分の過去の値を保持します
  • 新規購読時に保持している値をすべて再生します
  • メモリ使用量に注意が必要です

使用場面:

  • 直近のN件の履歴が必要な場合
  • アプリの状態変更の履歴管理
  • デバッグやログ記録

実装例:

// 最新3件の検索履歴を保持
let searchHistorySubject = ReplaySubject<String>.create(bufferSize: 3)

// 検索履歴の追加
searchHistorySubject.onNext("Swift")
searchHistorySubject.onNext("RxSwift")
searchHistorySubject.onNext("MVVM")

// 新しい購読者は最新3件を受け取る
searchHistorySubject
    .subscribe(onNext: { query in
        print("検索履歴: \(query)")
    })
    .disposed(by: disposeBag)

選択の基準:

  1. 値の保持が必要か?
  • 必要あり → BehaviorSubject
  • 必要なし → PublishSubject
  1. 過去の値が必要か?
  • 必要あり → ReplaySubject
  • 最新値のみ → BehaviorSubject
  • 不要 → PublishSubject
  1. メモリ使用量の考慮
  • ReplaySubjectは使用メモリが大きくなる可能性があるため、バッファサイズの設定に注意

この使い分けを意識することで、より効率的なReactiveプログラミングが実現できます。

使用パターン

典型的なSubscribe パターン

observable
    .subscribe(
        onNext: { value in
            // 次の値を処理
        },
        onError: { error in
            // エラーを処理
        },
        onCompleted: {
            // 完了を処理
        },
        onDisposed: {
            // 破棄時の処理
        }
    )

Behaviorパターン

// 初期値を持つBehaviorRelay
let relay = BehaviorRelay<String>(value: "初期値")

// 値の更新
relay.accept("新しい値")

// 現在値の取得
let currentValue = relay.value

Publishパターン

// PublishRelayの作成(エラーや完了を発生させない)
let relay = PublishRelay<String>()

// イベントの発行
relay.accept("新しいイベント")

RxSwift 重要概念解説

=====================================

DisposeBagの役割


DisposeBagは、RxSwiftのメモリ管理において重要な役割を果たします。

  • メモリリーク防止: Observableのサブスクリプションを適切に解放
  • 自動解放: クラスのdeinit時に全てのサブスクリプションを自動的に解放
  • 管理の簡素化: 複数のサブスクリプションをまとめて管理
class ExampleViewModel {
    private let disposeBag = DisposeBag()
    
    init() {
        // このサブスクリプションはViewModelが破棄されると自動的に解放される
        observable.subscribe(onNext: { value in
            // 処理
        })
        .disposed(by: disposeBag)
    }
}

initの役割


initは初期化メソッドとして以下の役割があります。

  • 初期設定: プロパティの初期化
  • 依存関係の注入: 必要なオブジェクトの注入
  • バインディングの設定: RxSwiftのストリーム設定

Hot Observable vs Cold Observable


Cold Observable

// Cold Observableの例
let coldObservable = Observable<Int>.create { observer in
    observer.onNext(Int.random(in: 1...100))
    return Disposables.create()
}

// 各サブスクライバーが異なる値を受け取る
coldObservable.subscribe(onNext: { print("Subscriber 1: \($0)") })
coldObservable.subscribe(onNext: { print("Subscriber 2: \($0)") })

Hot Observable

// Hot Observableの例
let subject = PublishSubject<Int>()
let hotObservable = subject.asObservable()

// 全てのサブスクライバーが同じ値を受け取る
hotObservable.subscribe(onNext: { print("Subscriber 1: \($0)") })
hotObservable.subscribe(onNext: { print("Subscriber 2: \($0)") })

subject.onNext(42)

よく使用されるRxSwiftコンポーネント


Subject Types

  • PublishSubject: 購読後の値のみを受け取る
  • BehaviorSubject: 最新値を保持し、新規購読時に提供
  • ReplaySubject: 指定した数の過去の値を保持

Relay Types

  • PublishRelay: エラーや完了を発生させないPublishSubject
  • BehaviorRelay: エラーや完了を発生させないBehaviorSubject

実践的な使用例


タイマー実装

Observable<Int>
    .interval(.seconds(1), scheduler: MainScheduler.instance)
    .map { _ in Date() }

ユーザー入力のデバウンス

searchTextField.rx.text
    .debounce(.milliseconds(300), scheduler: MainScheduler.instance)
    .distinctUntilChanged()

ネットワーク状態監視

Reachability.rx.isConnected
    .subscribe(onNext: { isConnected in
        print("ネットワーク状態: \(isConnected)")
    })

エラーハンドリング


observable
    .retry(3) // 3回までリトライ
    .catchError { error in
        // エラーハンドリング
        return Observable.just(fallbackValue)
    }

まとめ

以上がRXについて知りたい私が書いた記事でした。Swift以外でも出てくるので知っておくと理解をしやすくなるらしい。UIKitで試したかったが、SwiftUIの方が慣れているのでこちらでやってしまった。

Discussion