質問: 最近UniRxを使い始めたもので、UniRxのAddToについて質問です。 AddToを使うと自分で手動でDisposeしなくても引数で指定したObjectが破棄されたときに、自動的にサブスクリプションがDisposeされるのは分かったのですが、 例えばボタンに対して ### 該当のソースコード ``` public class Test : MonoBehaviour { [SerializeField] Button button; void Start() { button.OnClickAsObservable().Subscribe(_ => { Debug.Log("Click!"); }); } } ``` のようにした場合で、Testクラスとbuttonがほぼ同じライフサイクル(シーン遷移などでほぼ同時に作られて、ほぼ同時に削除される場合など) のときでもAddToしないと問題になりますでしょうか? イベントが飛んでくることはないのですが、メモリリークなどが起きてしまうのかが気になったもので。 また、どうのようなときにAddToを使うべきかなどアドバイスあれば幸いです。 よろしくお願いいたします。
回答: ご提示のコードを拝見する限りでは、`button`と`Test`のどちらが先に削除されても現状のコードにおいてはさほど問題はなさそうに思います。ですが今後どんどんコードを追加していって依存関係が複雑になると、場合によっては`AddTo`を行わないと問題が起きるかもしれません。 まず、`OnClickAsObservable`によって作られたストリームの寿命は`button`の寿命に依存していると考えられます。`button`が先に破壊されたとすると、そのタイミングで自動的にストリームが完了するので`Test`が行ったサブスクリプションもそこで解除されるでしょう。 逆に`Test`が先に破壊されたらどうなるかですが、今のところ`Test`の役割は`Start`のタイミングで`Subscribe`を行うだけであり、`Start`が終わったら`Test`はいてもいなくても動作に影響はないはずです。ですので、仮に下記のようにすると... ``` using UniRx; using UnityEngine; using UnityEngine.UI; public class Test : MonoBehaviour { SerializeField] Button button; void Start() { this.button.OnClickAsObservable().Subscribe(_ => { Debug.Log("Click!"); }); } void Update() { if (Time.time > 3.0f) { // 3秒後に自身を破壊 Debug.Log($"Destroy {this.name}"); Destroy(this.gameObject); } } } ``` 下図のように`Test`破壊後もサブスクリプションが解除されずに動作し続けるでしょう。 ![結果1 クリック時の動作が今回のような固定された文章を表示するだけであれば、サブスクリプションは`Test`が生きているかどうかに依存しませんので問題ないでしょう。 しかし、もし下記のように`Test`が生きていないとまずいような動作をさせている場合は... ``` using UniRx; using UnityEngine; using UnityEngine.UI; public class Test : MonoBehaviour { SerializeField] Button button; void Start() { // クリックされたら自身の座標を出力する // 正常に実行するには、クリックされた時点でこのオブジェクトが生きていなければならない this.button.OnClickAsObservable().Subscribe(_ => { Debug.Log(this.transform.position); }); } void Update() { if (Time.time > 3.0f) { // 3秒後に自身を破壊 Debug.Log($"Destroy {this.name}"); Destroy(this.gameObject); } } } ``` 下図のようにトラブルが発生する可能性があります。 ![結果2 このような場合に`AddTo`を行っておけば... ``` using UniRx; using UnityEngine; using UnityEngine.UI; public class Test : MonoBehaviour { SerializeField] Button button; void Start() { // 自身の破壊に連動してサブスクリプションを解除するよう設定 this.button.OnClickAsObservable().Subscribe(_ => { Debug.Log(this.transform.position); }).AddTo(this); } void Update() { if (Time.time > 3.0f) { // 3秒後に自身を破壊 Debug.Log($"Destroy {this.name}"); Destroy(this.gameObject); } } } ``` `Test`破壊後はボタンを押しても何も起こらないようになるでしょう。 ![結果3 ご質問者さんの場合は`Test`と`button`の破壊がほぼ同タイミングとのことですから、`Test`が破壊されて`button`がまだ生きている一瞬の間にクリックイベントが来ないかぎりはトラブルを起こさないだろうと予想されますが、念のため警戒しておいた方がいいかと思います。 `AddTo`を行うべきかどうかは依存関係を考慮して決めるのがいいだろうとは思いますが、とりあえず`AddTo`を行っておくという方針でもあまり害はなさそうな気もします。オブジェクトに自動的に`Dispose`処理担当のコンポーネントが追加されたりするようですので多少は処理コストがかかるのかもしれませんが、気軽に`AddTo`を使えなくなるほどのマイナス点ではなさそうに見えます。 `AddTo`の使いどころとして、説明文書の中ではObservable Lifecycle Managementの節に`AddTo`が登場しています。 `Observable.Timer`や`Observable.EveryUpdate`のような静的ジェネレーターで作ったストリームは手動で切らないと動きっぱなしになるとのことですので、それらをスマートに処分するのには便利そうですね。