Unity
UniRx

【UniRx】SubscribeのDisposeをGameObjectに紐付ける

More than 3 years have passed since last update.

UniRxについての記事のまとめはこちら


SubscribeとDispose

RxではObservableを購読してメッセージの待受けを行うことをSubscribeと呼び、その購読を中止することをDisposeと呼びます。
このSubscribeは気をつける点があり、Observableが破棄されたタイミングでちゃんとDisposeする必要があります。
Observableが破棄される際にOnCompletedが発行されれば自動的にDisposeされるのですが、ストリームによってはOnCompletedが発行されない場合もあります。

Observableの寿命を考慮せずに使った場合

まずは以下のコードとその実行結果を見てください。

using UnityEngine;
using UniRx;
using System;

public class ObservableLifeTime : ObservableMonoBehaviour
{
    public override void Start()
    {
        base.Start();

        //1秒ごとにメッセージを発信するObservable
        Observable.Timer(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1))
            .Subscribe(x => Debug.Log(x));

        //3秒後にGameObjectを削除する
        Invoke("DestroyGameObject", 3);
    }

    /// <summary>
    /// ログを吐いて削除する
    /// </summary>
    private void DestroyGameObject()
    {
        Debug.Log("Destroy");
        Destroy(this.gameObject);
    }
}

実行結果

ObservableLifeTime

DestroyでGameObjectが破棄されたにも関わらず、Observable.Timerで作ったストリームが稼働し続けてしまっています。
これはObservable.Timerで作ったObservableがstaticとして生成されてしまい、GameObjectとは関係なく独立して動作してしまっているためです。

AddToでSubscribeとGameObjectを紐付ける

AddToメソッドを使うとこの問題を簡単に解決することができます。

using UnityEngine;
using UniRx;
using System;

public class ObservableLifeTime : ObservableMonoBehaviour
{
    public override void Start()
    {
        base.Start();

        //1秒ごとにメッセージを発信するObservable
        Observable.Timer(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1))
            .Subscribe(x => Debug.Log(x))
            .AddTo(this.gameObject); //指定GameObjectの寿命に紐付ける

        //3秒後にGameObjectを削除する
        Invoke("DestroyGameObject", 3);
    }

    /// <summary>
    /// ログを吐いて削除する
    /// </summary>
    private void DestroyGameObject()
    {
        Debug.Log("Destroy");
        Destroy(this.gameObject);
    }
}

実行結果
ObservableLifeTimeDisposed

AddToを使うことで、SubscribeのDisposeを指定したGameObjectの寿命に紐付け、GameObjectのDestroy時に勝手にDisposeしてくれるようになりました
これでDisposeの管理を気にせずにファクトリメソッドが使えるようになりました!

ただし、OnCompletedが飛ぶ訳ではないのでその点は気をつけましょう。