MessagePipeのDisposable処理について

Qiita で記事化

qiita.com


以下の環境で実行しています

  • MacOS
  • Unity2020.2.0b2
  • MessagePipe v1.6.1
  • Zenject

MessagePipeはSubcribeでイベントの購読処理を行います。

public class Hoge
{
    public Hoge()
    {
        // MyEventの通知を受け取る
        var subscriber = MessagePipe.GlobalMessagePipe.GetSubscriber<MyEvent>();
        subscriber.Subscribe(ev =>
        {
            // 購読処理
        });
    }
}

Subscribeの戻り地はIDisposableです。 このIDisposableを適切に処理しなければメモリリークが発生します。

つまり、購読クラスが破棄されたとしても、イベント購読し続けることになります。

(バグが出る可能性が高い!

public class Hoge
{
    public Hoge()
    {
        // MyEventの通知を受け取る
        var subscriber = MessagePipe.GlobalMessagePipe.GetSubscriber<MyEvent>();
        subscriber.Subscribe(ev =>
        {
            // Hogeインスタンスが消えたとしても、ここは永遠に残り続ける
            // MyEventが来るたびに呼び出され続ける
        });
    }
}

基本的には購読しているクラスが破棄されると同時にイベントの購読も消したいはず。

今回はイベントの破棄を担当するDisposeについて書いていきます。

非Monobehaviourクラスでイベントを破棄したい場合

public class Hoge
{
    private readonly IDisposable _disposable;

    public Hoge()
    {
        var bag = DisposableBag.CreateBuilder();
        
        // MyEventの通知を受け取る
        {
            var subscriber = MessagePipe.GlobalMessagePipe.GetSubscriber<MyEvent>();
            subscriber.Subscribe(ev =>
            {
                // 購読処理
            }).AddTo(bag);
        }

        // MyEvent2の通知を受け取る
        {
            var subscriber = MessagePipe.GlobalMessagePipe.GetSubscriber<MyEvent2>();
            subscriber.Subscribe(ev =>
            {
                // 購読処理
            }).AddTo(bag);
        }

        disposable = bag.Build();
    }

    public void RemoveEventAll()
    {
        // Disposeですべて破棄される。必ず終了前に呼び出すこと
        disposable.Dispose();
    }
}

Subscribeの戻り値に対してAddToメソッドを呼び出す。

そこに DisposableBag.CreateBuilder() で作り出した DisposableBagBuilder を渡して Build -> Dispose を呼び出すことでイベントすべての購読が破棄されます。

Disposeは明示的に呼び出す必要があるので注意


Monobehaviourを継承しているクラスでイベントを破棄したい場合

UniRxを組み込んでいる場合、Monobehaviourクラスはもう少し楽に破棄の処理ができます

namespace UniRx; // UniRx必須

public class Hoge : Monobehaviour
{
    void Awake()
    {
        // MyEventの通知を受け取る
        var subscriber = MessagePipe.GlobalMessagePipe.GetSubscriber<MyEvent>();
        subscriber.Subscribe(ev =>
        {
        }).AddTo(this); // 自分を渡してGameObjectの破棄時に削除をしてもらう
    }
}

AddTo に自分自身を渡してGameObjectのライフサイクルに結びつけます。

これでGameObjectが破棄されるときにイベントも破棄されます。


Subscribeを適切にハンドリングしてない場合エラーを出す

Dispose周りを見てきましたが、『Subcribeを適切に処理しているかどうか』は結局コードを書く人に委ねられている状態です。

忘れないようにしていても忘れたり抜けは必ず発生します。


しかし! なんとUnity2020.2以降であればSubcribeの戻り値をハンドリングしてないものがあれば、ビルド時にエラーになる便利なものが公開されているようです

Roslyn analyzers という、ユーザー独自のコードチェックを組み込める仕組みを利用されている。

(Unityは2020.2以降から使用できる)

docs.unity3d.com


結果からいうと MessagePipe.Analyzer.dll を組み込むことで

Subscribeに対して何も処理をしていない場合 Unityのコンソール上やVisualStudio上でエラーが出るようになります

VisualStudio f:id:toshizabeth:20210704173534p:plain

Unity f:id:toshizabeth:20210704173442p:plain


Subscribeは処理すべきものだと言うことで是非組み込んで有効活用していきたいところ。


導入手順

Unity2021.2.0b2 で動作を確認しています。 Unity2020.3.X系だとUnityでエラーが出ませんでした...(VisualStudioではエラーが出る)

バグが自分の設定が悪いのか.. 調査中


1.MessagePipe.Analyzer.dll を Unityに入れる

MessagePIpe の GithubMessagePipe.Analyzer.dll が上がっています。

(以下のGithubのReleaseを参照) github.com

こちらの MessagePipe.Analyzer.dll をダウンロードします。

f:id:toshizabeth:20210630233610p:plain


そしてAsset以下のどこか適当なディレクトリにぶっこむ。

f:id:toshizabeth:20210704174052p:plain


2. 設定変更

MessagePipe.Analyzer の Inspector を以下のように変更する

  • SelectPlatforms for Plugin のチェックをすべて外す
  • 右下のAssetLabelsボタンをクリックして RoslynAnalyzer という文字を入力してEnterを押してラベルを付ける

f:id:toshizabeth:20210704174456p:plain

ロード終了後、Subscribeを処理していない箇所に対してUnityがエラーを出すようになります


3. Visualstudioでもエラーを出す

VisualStudio上でもエラーを出すためには、Unityに対して更に一工夫する必要がありました。

同じくCysharpが公開しているCsprojModifierを導入します

github.com


公開されている最新バージョンのパッケージをインストールします

github.com


Unity > Project Settings > C# Project Modifier を開き、Add Roslyn Analyzer references to .csproj にチェックを付け、 「Regenerate project files」を押して .csproj を作り直します。

f:id:toshizabeth:20210704175014p:plain

終了後再度VIsualStudioを開き直したらVisualStudio上でも検知できるようになりました!


(現状、Unity2020.2以降のバージョンを使用している場合、VisualStudioCode, Rider ではこの機能は使用できないようです。

MacでVisualStudioでは正常に確認ができました)

Unityでエラーが出る以上実行ができないのでUnityでカバーしていればまあよし!とも捉えられる


MessagePipeDiagnostics を使用する

MessagePipeは更に機能があり(凄い)、実行中のプロジェクトで現在Subscribeされている数や情報を取得できる機能があります。(MessagePipeDiagnosticsInfo)

そして気軽に情報を見れるようにSubscribeしている箇所のモニターが出来る拡張機能が用意してあります。

Window > MessagePipeDiagnostics

を開くと、EditorWindowが立ち上がります。

f:id:toshizabeth:20210701100112p:plain

そして実行中にWindowを見てみると以下画像のようにSubsribeしている箇所が確認できます(便利

f:id:toshizabeth:20210704181756p:plain


この機能を利用するには GlobalMessagePipe.SetProvider を予め設定しておく事と、optionのEnableCaptureStackTraceをtrueにしておく必要があります。

f:id:toshizabeth:20210704181941p:plain