UniRx: AddTo() 拡張メソッドのソースを読む

この記事では、AddTo() 拡張メソッドを用いると、ゲームオブジェクトの破棄タイミングで購読を破棄する仕組みを眺めていきます。

次のサンプルコードでは、Observable.Interval() で一定時間ごとにログを出力します。 また、スペースキーを押すとシーンが切り替わるようにしています。

public class Main : MonoBehaviour
{   
    void Start()
    {
        var s = Observable.Interval(TimeSpan.FromSeconds(1));
        s.Subscribe(e => Debug.Log("e: " + e));
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // スペースキーでシーンを切り替える
            SceneManager.LoadScene("SampleScene2");
        }
    }
}

実行結果です。スペースキーを押してシーンが切り替わると "SampleScene2 Start" とログを出力するようにしています。

e: 0
e: 1
e: 2
SampleScene2 Start // ←シーンが切り替わった
e: 3
e: 4

このようにシーンが切り替わっても、Observable.Interval() は通知し続けます。 AddTo() 拡張メソッドを使うと、指定のゲームオブジェクトが破棄されたタイミングで購読を破棄することができます。

AddTo() 拡張メソッドのサンプルコード

public class Main : MonoBehaviour
{   
    void Start()
    {
        var s = Observable.Interval(TimeSpan.FromSeconds(1));
        s.Subscribe(e => Debug.Log("e: " + e)).AddTo(this); // AddTo を追加
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            SceneManager.LoadScene("SampleScene2");
        }
    }
}

実行結果です。

e: 0
e: 1
e: 2
SampleScene2 Start

今度はシーン遷移後は、Observable.Interval() の通知がログには出ておらず購読が破棄されていることが確認できます。

AddTo() 拡張メソッドのソースを読む

AddTo() は次のような拡張メソッドとして定義されています。

public static partial class DisposableExtensions
{
    /// <summary>Dispose self on target gameObject has been destroyed. Return value is self disposable.</summary>
    public static T AddTo<T>(this T disposable, GameObject gameObject)
        where T : IDisposable
    {
        if (gameObject == null)
        {
            disposable.Dispose();
            return disposable;
        }

        var trigger = gameObject.GetComponent<ObservableDestroyTrigger>();
        if (trigger == null)
        {
            trigger = gameObject.AddComponent<ObservableDestroyTrigger>();
        }

#pragma warning disable 618

        // If gameObject is deactive, does not raise OnDestroy, watch and invoke trigger.
        if (!trigger.IsActivated && !trigger.IsMonitoredActivate && !trigger.gameObject.activeInHierarchy)
        {
            trigger.IsMonitoredActivate = true;
            MainThreadDispatcher.StartEndOfFrameMicroCoroutine(MonitorTriggerHealth(trigger, gameObject));
        }

#pragma warning restore 618

        trigger.AddDisposableOnDestroy(disposable);
        return disposable;
    }
}    

gameObject に対して ObservableDestroyTriggerコンポーネント追加しているようです。  ObservableDestroyTrigger.AddDisposableOnDestroy() の中身を見てみましょう。 (gameObject が deactive のときの処理は、ここでは省略します)

[DisallowMultipleComponent]
public class ObservableDestroyTrigger : MonoBehaviour
{
    bool calledDestroy = false;
    Subject<Unit> onDestroy;
    CompositeDisposable disposablesOnDestroy;
    
    // (コード一部省略)

    /// <summary>This function is called when the MonoBehaviour will be destroyed.</summary>
    void OnDestroy()
    {
        if (!calledDestroy)
        {
            calledDestroy = true;
            if (disposablesOnDestroy != null) disposablesOnDestroy.Dispose();
            if (onDestroy != null) { onDestroy.OnNext(Unit.Default); onDestroy.OnCompleted(); }
        }
    }
    
    // (コード一部省略)

    public void AddDisposableOnDestroy(IDisposable disposable)
    {
        if (calledDestroy)
        {
            disposable.Dispose();
            return;
        }

        if (disposablesOnDestroy == null) disposablesOnDestroy = new CompositeDisposable();
        disposablesOnDestroy.Add(disposable);
    }
}
  1. ObservableDestroyTrigger.AddDisposableOnDestroy() で disposable を追加
  2. OnDestroy() のタイミングで 1. で登録した disposable を破棄

以上より、MonoBehaviour の OnDestroy() が呼び出されるタイミングで購読を解除されることが確認できました。

  • UniRx のバージョン: Ver 6.1.2

参考