🐉
RXの世界を学んでみた
RXSwiftを使って学習してみる
リアクティブプログラミングの概念が必要なプロジェクトに入った。「ビーヘービアー」とか聞き慣れない単語が出てきたので勉強してみる。
参考になったサイト
サンプルというけど難しそう💦
まずは単語から理解したい
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)
選択の基準:
- 値の保持が必要か?
- 必要あり → BehaviorSubject
- 必要なし → PublishSubject
- 過去の値が必要か?
- 必要あり → ReplaySubject
- 最新値のみ → BehaviorSubject
- 不要 → PublishSubject
- メモリ使用量の考慮
- 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