MessagePipeのDisposable処理について
Qiita で記事化
以下の環境で実行しています
- 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以降から使用できる)
結果からいうと MessagePipe.Analyzer.dll を組み込むことで
Subscribeに対して何も処理をしていない場合 Unityのコンソール上やVisualStudio上でエラーが出るようになります
VisualStudio
Unity
Subscribeは処理すべきものだと言うことで是非組み込んで有効活用していきたいところ。
導入手順
【Unity2021.2.0b2 で動作を確認しています。 Unity2020.3.X系だとUnityでエラーが出ませんでした...(VisualStudioではエラーが出る)】
バグが自分の設定が悪いのか.. 調査中
1.MessagePipe.Analyzer.dll を Unityに入れる
MessagePIpe の Github に MessagePipe.Analyzer.dll が上がっています。
(以下のGithubのReleaseを参照) github.com
こちらの MessagePipe.Analyzer.dll をダウンロードします。
そしてAsset以下のどこか適当なディレクトリにぶっこむ。
2. 設定変更
MessagePipe.Analyzer の Inspector を以下のように変更する
- SelectPlatforms for Plugin のチェックをすべて外す
- 右下のAssetLabelsボタンをクリックして
RoslynAnalyzer
という文字を入力してEnterを押してラベルを付ける
ロード終了後、Subscribeを処理していない箇所に対してUnityがエラーを出すようになります
3. Visualstudioでもエラーを出す
VisualStudio上でもエラーを出すためには、Unityに対して更に一工夫する必要がありました。
同じくCysharpが公開しているCsprojModifierを導入します
公開されている最新バージョンのパッケージをインストールします
Unity > Project Settings > C# Project Modifier
を開き、Add Roslyn Analyzer references to .csproj にチェックを付け、 「Regenerate project files」を押して .csproj を作り直します。
終了後再度VIsualStudioを開き直したらVisualStudio上でも検知できるようになりました!
(現状、Unity2020.2以降のバージョンを使用している場合、VisualStudioCode, Rider ではこの機能は使用できないようです。
MacでVisualStudioでは正常に確認ができました)
Unityでエラーが出る以上実行ができないのでUnityでカバーしていればまあよし!とも捉えられる
MessagePipeDiagnostics を使用する
MessagePipeは更に機能があり(凄い)、実行中のプロジェクトで現在Subscribeされている数や情報を取得できる機能があります。(MessagePipeDiagnosticsInfo)
そして気軽に情報を見れるようにSubscribeしている箇所のモニターが出来る拡張機能が用意してあります。
Window > MessagePipeDiagnostics
を開くと、EditorWindowが立ち上がります。
そして実行中にWindowを見てみると以下画像のようにSubsribeしている箇所が確認できます(便利
この機能を利用するには GlobalMessagePipe.SetProvider
を予め設定しておく事と、optionのEnableCaptureStackTrace
をtrueにしておく必要があります。