😇

【UniRx】Subjectをコンポーネントのインスタンス変数にした場合の寿命

2025/02/05に公開

はじめに

Component.UpdateAsObservable() 等のUniRxの拡張メソッドは、そのコンポーネントが付与されたGameObjectのDestroyのタイミングでComplete状態へ移行するようになっています。

このため、下記のように AddTo(this) は不要です。

public class SomeComponent : MonoBehaviour
{
    private void Start()
    {
        this.UpdateAsObservable()
            .Subscribe(_ => DoSomething());
        //  .AddTo(this)
        //  ↑ GameObjectのDestroyでCompleteになるのでAddTo(this)は不要
    }
}

では自作の Subject はどうなのでしょうか?

ストリーム停止条件を記述しない場合

ストリーム停止条件を記載しない書き方
public class EventFiringComponent : MonoBehaviour
{
    private readonly Subject<bool> someSubject = new Subject<bool>();
    public IObservable<bool> SomeObservable => this.someSubject;
}

public class SubscribingComponent : MonoBehaviour
{
    public void SubscribeEvents(EventFiringComponent component)
    {
        component.SomeObservable.Subscribe(_ => DoSomething());
    }
}

まずはシンプルに書いた場合の挙動を考えます。
この場合、ストリームが停止するのは、 Subject を持つコンポーネントが付与されたGameObjectがDestroyされた後にGCが走るタイミングとなります。
それはDestroy後すぐかもしれないし、アプリケーション終了のタイミングかもしれません。
メモリ使用制限が厳しい実行環境ではこれがネックとなる可能性はありますが、記述量の少なさや装飾が少ないことによる可読性の高さといった利点を考えると、この書き方を採用する場面も往々にしてあると思います。

コンポーネントと寿命を同じにする方法

Component.UpdateAsObservable() 等と同じようにコンポーネントの付与されたGameObjectのDestroyでストリームが停止するようにするためには、以下のように書くことになります。

ストリームソースの寿命をコンポーネントに合わせる書き方
public class EventFiringComponent : MonoBehaviour
{
    private readonly Subject<bool> someSubject = new Subject<bool>();
    public IObservable<bool> SomeObservable => this.someSubject;

    private void OnDestroy()
    {
        this.someSubject.OnCompleted();
    }
}

public class SubscribingComponent : MonoBehaviour
{
    public void SubscribeEvents(EventFiringComponent component)
    {
        component.SomeObservable.Subscribe(_ => DoSomething());
    }
}

これでDestroyのタイミングでストリームソースがComplete状態へと移行することになり、 Component.UpdateAsObservable() 等と同じライフサイクルになります。

「UniRxの拡張メソッドとライフサイクルが同じ」、「コンポーネントの寿命とストリームソースの寿命は同じ」という仕様のわかりやすさは非常に魅力的です。
また、UniRx内ではComplete状態になると不要になった内部の参照を切るように工夫されており、メモリ効率が上がることも期待できます。

しかし、1行のみとは言え、 Subject を多く保有しているクラスでは記述量の増加が結構辛いのと、宣言箇所と停止条件を書く場所が離れるので、記述漏れが発生しそうで怖いです。
シーン内の一部品では過剰設計となるかもしれませんが、実行環境のメモリ使用制限が厳しい場合やフレームワークなどの汎用部品では使えそうです。

リリテックラボ

Discussion