はじめに
RxSwiftを利用したコード内で以下の定義がされていることがよくある。
/// RXのゴミ箱
let disposeBag = DisposeBag()
/*
* 省略
*/
func bind() {
hogehoge.subscribe(onNext: { [weak self] value in
// 何らかの処理
}).disposed(by: disposeBag)
}
このコードはおまじないのように書かれており、コメントにはRxのゴミ箱
と記述されていたりすることがあってRx初心者にとっては意味がわからなかった。
そしてsubscribeしているところでメソッドチェーンで.disposed(by: disposeBag)
という定義がされており、ゴミ箱に捨てられてるな??みたいな感じになってフワフワしたままだったので、今一度見直すことにした。
DisposeBag
役割
適当なObservableをsubscribe
したとき、戻り値はDisposableオブジェクト
なので、dispose()
を呼び出せば講読解除できる。
講読解除をしないとメモリリークの原因となるので、解除はしておきたい。
しかしdispose()
は自分の好きなタイミングで呼ぶことができるので、一つのクラスで大量のObservableを監視していた場合、都度disposeしてあげなければいけないし、ヒューマンエラー等で抜け漏れる可能性がある。
そこで使用するのがDisposeBag
である。
公式のコード上のコメントには下記のように記されている。
Thread safe bag that disposes added disposables on `deinit`.
要するに、deinit
時に自動的にDisposeBagに格納されているDisposableオブジェクトを解放してくれるようだ。
書き方
let disposeBag = DisposeBag() //(1)
//(2-1)
hogehoge.subscribe(onNext: { [weak self] value in
// 何らかの処理
}).disposed(by: disposeBag)
// or
//(2-2)
let disposable = hogehoge.subscribe(onNext: { [weak self] value in
// 何らかの処理
})
/*
* 任意の場所↓↓↓
*/
disposable.disposed(by: disposeBag)
(1)で定義をする。DisposeBagはDisposableオブジェクトを格納してくれるので一つだけ定義すれば良い。
これがゴミ箱の定義。
(2-1),(2-2)はやっていることは変わらない。
メソッドチェーンを使ってdisposed(by: disposeBag)
を繋げて記述しても良いし、一度プロパティとしてから別途任意な場所で記述するのもOK。
個人的には、コードが散らばらずsubscribeしている箇所を見ればまとまっており、どこでdisposed
しているかを探す必要がなくなるメソッドチェーンを使った(2-1)が推し。
講読解除(dispose)されるタイミング
- 講読対象(Observable)を監視しているとき、
onCompleted
もしくはonError
が発生してイベントが終了したら - (DisposeBagに監視対象を渡している前提で、) その監視をしているクラスが解放されたら
後者のクラスが解放される時というのは以下のような流れのイメージである。
- 画面AからBに遷移する(Bのインスタンスが作成される)
- Bで何かしらの値を監視する(Bのファイル内でsubscribeが行われる)
- 画面Bを閉じる(ARCにより参照カウントが0になることでBのインスタンスが解放される)
3のタイミングのBのインスタンス解放時にDisposaBagに格納されているオブジェクトが解放される。
終わりに
普段何気なく使っていたDisposeBagですが、監視しているオブジェクトをまとめて解放してくれてメモリリークの原因となることを回避してくれているということがわかりました。
もし、ここが違うよとかがあればご教授頂ければ幸いです。