WeakEventパターンの実装

必要性

たぶん必要になるのはアーキテクチャ周りぐらいだと思う。
ViewModelがソースのイベントを購読する場合、ソースがViewModelの参照を持つ。寿命の長いソースが寿命の短いViewModelの参照を捕捉することになるので、いつまで経ってもViewModelはGCに回収されない。これが原因でメモリーリークする。

ViewModelがいらなくなった時点でイベントの購読を解除できるんなら問題はないんだけど必ずその処理を実行できる状況とは限らない。たとえば、ViewModelのメンバーとして別のViewModelを公開する場合。破棄するタイミングだとか、管理するViewModelの数によってはやってらんない。
そんなとき頼りになるのがWeakEventパターンというやつ。

仕組

通知の発行前に弱参照を一枚かませる。これだけ。

標準ライブラリとして用意されているのはWeakEventManagerクラスとIWeakEventListnerインターフェイス。これらを使うのが割とスタンダードなんだと思う。それ以外にもブログや、Livet、Prismなどのライブラリで、それぞれいろんなアプローチによって実装されている。

理想

現状での要望としては
・ ソース側には手を加えない
・ Disposeメソッドによる解除も可能
・ ハンドラーに対してジェネリック対応(型推論も可能な限り使いたい)

実装案

今回はManagerクラスを使わずに実装する。
まずはベース兼、staticメソッドによって派生したリスナーを生成するクラス。
イベント引数は型推論がきかないので明記するしかない。

これを継承するクラスを2パターン。

  • パターンA

  • パターンB

で、こう使う。

考察

パターンAでは、ソースはリスナーを強参照し、リスナーはViewModelを弱参照する。
ソースからの通知を受信したときViewModelの存在をチェックして、あれば発行、なければリスナー自身に対する強参照をソースから外す、という流れ。だからViewModelがリスナーを生成するときに、引数のラムダ式にフィールド変数やローカル変数を使えない。これをやってしまうと、リスナーがViewModelを強参照することになってViewModelは回収されなくなる。
利点は、ViewModelは生成されるリスナーを保持しとかなくても構わないこと。解除のタイミングを制御したいときだけ使えばよろし。

これに対してパターンBは、ソースはリスナーを弱参照し、リスナーはViewModelを強参照する。
リスナーを強参照しとかないとGCにあっけなく回収されてしまうのでリスナーの保持は必須。その代り、引数のラムダ式にパターンAのような制限はない。数も少ない。

パターンAかB、どっちかがあれば事足りる。どっちがいいかというと好みとか、その場の状況とか、正直なところどっちでもいい。
ただ実際に使ってみて、面倒だなと思うところもあった。毎度毎度決まりきったラムダ式を書かにゃならんことと、各イベントに対してそれぞれリスナーを作成せにゃならんこと。Disposableなコレクションでリスナーを一括管理すればいくらか手間を省けるけど、使うとき自前で用意せにゃならんこと。
この辺りを考慮して、気が向いた遠い未来、また書いてみるかも。

About houzkin

Leave a comment

*

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)