UniRx には、GameObjectが死んだときに自動でキャンセルする方法が2つほど用意されてる。
一つ目は、 AddTo(...)
で 、死んだら止めるリストにDisposableを登録しておく方法。
// MonoBehaviourのとあるメソッド内
observable
.Subscribe(...)
.AddTo(this) // thisが死んだら Disposeされる
二つ目は、.TakeUntileDestroy(...)
オペレータを使い、指定したGameObjectが死んだら 即Completedする。
// MonoBehaviourのとあるメソッド内
observable
.TakeUntilDestroy(this)
.Subscribe(...) // thisが死んだら OnCompleted() からの Dispose()
この2つ、並べてみると どちらも購読側でほぼ同じようなスタイルで使えてしまうので、どっちで書くか? という話になる。
で、実装を読んでみた結果、近頃は、基本的に、発行側も購読側も、TakeUntilDestroy
を使うのが良いという結論になった。
理由は、AddTo
で追加したDisposable は、明示的にリムらない限りCompositeDisposableへ蓄積されていくため、ゴミオブジェクトが増え続けるコードを書くリスクがあるから。
対してTakeUntilDestroy は、内部的には HotなObservableを待っているだけなので数が増えることによるオーバーヘッドはない。
1つのMonoBehaviourが実行するSubscribeの回数が固定であればこれは問題にならないんだけど、Subscribeは非同期処理の開始命令としても使えてしまうから、実行中に何度も何度もやっちまうコードを素朴に書くと、保持しているリストの中身が天井知らずに増殖してゆくことになる。
そのため 最近は TakeUntilDestroy のスタイルで書くようになった。
この辺のスタイルはコードのあちこちに現れてくるので、GameObjectへのAddToはなくして良いのではないか という気がする。
--
Rxというやつは、キャンセルがきちんとサポートされていることがオシャレな点のひとつ。
この世の 全てのObservable は、自身をキャンセル(Dispose)する方法を知っている。
Subscribe
で購読を開始したとき、どんなObservable も IDisposable
を返してくるので、こいつを握っておいてDispose()すればいつでも処理はキャンセルされることになっている。
行儀の良いObservableたちは、Disposeで単に静かになるだけじゃなく、リソースを食う非同期処理も中断してくれるため、こういった機能がないPromiseなんかと比べるとたいへん省エネな実装ができる。
Rx を使っていると、「リソースがリークしていないか」という知能指数が要求される難しい問題が、「適切にDisposeを呼んでいるか」というそこそこ単純な問題に置き換わってくれるで嬉しい。