この記事でのバージョン
Unity 2021.3.11f1
UniRx 7.1.0
概要
UniRxの主な使い方としてIObservableに対してSubscribeして処理を書く、という使い方をよくしますが、これだけではメモリリークやクラッシュの原因になる可能性があります。
これを防ぐにはSubscribeした戻り値IDisposableに対してDisposeという関数を呼んで購読を解除する必要があります。
このDisposeを呼ぶ方法には何パターンか方法があるため代表的なものをいくつか紹介します。
Observable側のコード
このPlayerクラスのOnJumpObservableに対してSubscribeすることとします。
public class Player : MonoBehaviour { private readonly Subject<Unit> _onJumpSubject = new(); public IObservable<Unit> OnJumpObservable => _onJumpSubject; private void Update() { if (Input.GetKey(KeyCode.Space)) { // ジャンプ処理 _onJumpSubject.OnNext(Unit.Default); } } }
AddToを使うパターン
最も一般的なパターンです。
public class SubscribeTest01 : MonoBehaviour { [SerializeField] private Player _player; private void Awake() { _player.OnJumpObservable.Subscribe(_ => { Debug.Log("ジャンプした!"); }).AddTo(this); } }
SubscribeのあとにAddToという関数を呼んでthis(Component)を渡しています。これで、このComponentが付いているGameObjectがDestroyされたときにDisposeする、という意味になります。
処理がMonoBehaviourに紐づいている場合はこちらを使うのがおすすめです。
IDisposableを受け取るパターン
MonoBehaviourを使用していないときによく使用するパターンです。
public class SubscribeTest02 : IDisposable { private Player _player; private IDisposable _disposable; public void Setup(Player player) { _player = player; _disposable = _player.OnJumpObservable.Subscribe(_ => { Debug.Log("ジャンプした!"); }); } public void Dispose() { _disposable?.Dispose(); } }
このパターンはSubscribeTest02クラスがMonoBehaviour継承ではないため、AddToの代わりにIDisposableを受け取り、自身でDisposeを行うようにしています。(そのため、自分自身もIDisposableを実装しています)
また_disposable?.Dispose()の部分は_disposableがnullじゃなければDisposeを実行する、という意味になります。
CompositeDisposableを使うパターン
Disposeするものが多いかつMonoBehaviourに紐づいてない場合に使えるパターンです。
public class SubscribeTest03 : IDisposable { private Player _player; private readonly CompositeDisposable _compositeDisposable = new(); public void Setup(Player player) { _player = player; _compositeDisposable.Add(_player.OnJumpObservable.Subscribe(_ => { Debug.Log("ジャンプした!"); })); // ↓でもOK _player.OnJumpObservable.Subscribe(_ => { Debug.Log("ジャンプした!!"); }).AddTo(_compositeDisposable); } public void Dispose() { _compositeDisposable.Dispose(); } }
CompositeDisposableを使うと複数のDisposableをまとめてDisposeすることができます。
上記のように複数のSubscribeを同タイミングでDisposeしたい場合は非常に便利に使うことができます。
Observable側のDisposeについて
本来はObservable側にもDisposeを書いた方がベターです。
(ただちに問題にはなりにくいですが)
最初のコードも以下のように書くとよりよいです。
public class Player : MonoBehaviour { private Subject<Unit> _onJumpSubject = new(); public IObservable<Unit> OnJumpObservable => _onJumpSubject; private void Awake() { // ① あらかじめAddToしておくパターン _onJumpSubject.AddTo(this); } private void Update() { if (Input.GetKey(KeyCode.Space)) { // ジャンプ処理 _onJumpSubject.OnNext(Unit.Default); } } private void OnDestroy() { // ② Destroy時にDisposeしてしまうパターン _onJumpSubject.Dispose(); } }
①か②のどちらか1つで大丈夫です。
まとめ
SubscribeのDispose忘れは必ずエラーになるとは限らず、発見されづらいため後々やっかいな問題になる可能性があります。
まずはAddToを付けるようにする、というところからでよいと思うので、とにかくSubscribeを使用するときはいつDisposeするかを同時に考える、ということを癖づけることをおすすめします。