Xamarin.FormsでMessagingCenterのSubscribeとUnsubscribeの注意点


Subscribeは累積する

Xamarin.Formsにはわりと気軽に画面間でデータ(オブジェクト)のやりとりができるMessagingCenterというものがあります。
これは静的クラスでデータを受け取りたい側でSubscribeメソッドで受け取る準備(購読)をし、送りたい側でSendメソッドでデータを送れば、購読先でデータを受け取ることができるので、とても便利です。

さて、このMessagingCenter.Subscribeメソッドですが、呼び出すたびに購読が累積していきます。
例えば、以下のように2回購読したとしましょう。
これはメッセージが届いたらダイアログを表示させるだけのものです。

        public MainPage()
        {
            InitializeComponent();

            MessagingCenter.Subscribe<MainPage, string>(this, "Greeting", (sender, msg) => {
                DisplayAlert("Message1", msg+"1", "OK");
            });

            MessagingCenter.Subscribe<MainPage, string>(this, "Greeting", (sender, msg) => {
                DisplayAlert("Message2", msg+"2", "OK");
            });

        }

で、例えば以下のようにボタンが押されたタイミングでSendメソッドでメッセージを送ると、2回ダイアログが表示されます。

        private void Button_Clicked(object sender, EventArgs e)
        {
            MessagingCenter.Send<MainPage, string>(this, "Greeting", "Hello");
        }

それは正しい動作なのですが、Subscribeを累積したくない場合はUnsubscribeして購読を解除する必要があります。

購読解除するときの注意点

ざっくりいうと、購読を適切に解除するにはUnsubscribeメソッドを用いるのですが、ここで注意が必要なことは型引数と引数が解除したい購読と同じであることです。

public static void Unsubscribe<TSender,TArgs> (object subscriber, string message) where TSender : class;

例えば上の例で言えば、以下の購読を解除したい場合、

            MessagingCenter.Subscribe<MainPage, string>(this, "Greeting", (sender, msg) => {
                DisplayAlert("Message1", msg+"1", "OK");
            });

購読解除は以下のようになります。

            MessagingCenter.Unsubscribe<MainPage, string>(this, "Greeting");

しかし、ここでもう一つ要注意なのがsubscriber引数です。
公式のドキュメントではSubscribeでもUnsubscribeでも以下のように書かれているため、さしあたりthisにしていると思います。

パラメーター
subscriber
Object
メッセージをサブスクライブしているオブジェクト。 通常、サブスクライブしているオブジェクト内で使用される this キーワードによって指定されます。

多くの場合これは正しく機能するのですが、プログラムによってはthisが毎回異なる場合があり、解除したい購読を正しく指定できずにどんどん購読が累積していくことになります。
例えば、毎回newされるオブジェクトの中で購読や解除をしようとする場合です。

subscriber引数には、変化しないインスタンスを指定する必要があります。
thisが変化しないインスタンスであればそれを使用すればよいし、そうでなければ変化しないインスタンスを用意(例えばシングルトンなオブジェクト)してそれを使用する必要があるようです。

ちなみに

Xamarin.Forms 4.0(現時点ではPreview)には新しいコンテナのShellがありますが、これをプロジェクトテンプレートから作成すると、このテンプレートで作成されるプロジェクトは購読の解除がうまくできずに累積してしまいます。

ItemsViewModelクラスで以下のように購読と解除を行うように変更するとうまくいきます。

        public ItemsViewModel()
        {
            Title = "Browse";
            Items = new ObservableCollection<Item>();
            LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());

            MessagingCenter.Unsubscribe<NewItemPage,Item>(MessagingCenter.Instance, "AddItem");
            MessagingCenter.Subscribe<NewItemPage, Item>(MessagingCenter.Instance, "AddItem", async (obj, item) =>
            {
                var newItem = item as Item;
                Items.Add(newItem);
                await DataStore.AddItemAsync(newItem);
            });
        }