疎結合コンポーネント間の通信
Note
この電子ブックは 2017 年春に発行されたもので、その後は改訂されていません。 このブックには今なお価値のある内容が多く含まれていますが、一部の記載内容は古くなっています。
発行/サブスクライブ パターンは、パブリッシャーがサブスクライバーと呼ばれる受信者を知らずに、メッセージを送信するメッセージング パターンです。 同様に、サブスクライバーは、パブリッシャーを知らずに特定のメッセージをリッスンします。
.NET のイベントでは、発行/サブスクライブ パターンが実装されます。こうしたイベントは、コントロールやそれを含むページなど、疎結合が不要な場合に、コンポーネント間の通信レイヤーに対して最もシンプルで簡単な方法です。 ただし、パブリッシャーとサブスクライバーの有効期間はオブジェクト参照によって相互に結合され、サブスクライバーの種類にはパブリッシャーの種類への参照が含まれている必要があります。 これにより、特に、静的なオブジェクトまたは有効期間の長いオブジェクトのイベントをサブスクライブする、有効期間が短いオブジェクトがある場合に、メモリ管理の問題が発生する可能性があります。 イベント ハンドラーが削除されていない場合、サブスクライバーは、パブリッシャー内のサブスクライバーへの参照によって保持されます。これにより、サブスクライバーのガベージ コレクションが妨げられるか、または遅延します。
MessagingCenter の概要
Xamarin.FormsMessagingCenter
クラスでは、パブリッシュ/サブスクライブ パターンが実装され、オブジェクトと型の参照によってリンクしにくいコンポーネント間で、メッセージ ベースの通信を行うことができます。 このメカニズムにより、パブリッシャーとサブスクライバーは相互に参照することなく通信できるため、コンポーネント間の依存関係を減らすことができます。また、コンポーネントを個別に開発およびテストすることもできます。
MessagingCenter
クラスでは、マルチキャストの発行/サブスクライブ機能を提供します。 つまり、1 つのメッセージをパブリッシュする複数のパブリッシャーが存在し、同じメッセージをリッスンしている複数のサブスクライバーが存在する可能性があるということです。 図 4-1 はこの関係を示したものです。
Figure 4-1: マルチキャストのパブリッシュ/サブスクライブ機能
パブリッシャーは MessagingCenter.Send
メソッドを使用してメッセージを送信しますが、サブスクライバーは MessagingCenter.Subscribe
メソッドを使用してメッセージをリッスンします。 さらに、サブスクライバーは、必要に応じて MessagingCenter.Unsubscribe
メソッドを使用して、メッセージ サブスクリプションを解除することもできます。
内部的には、MessagingCenter
クラスでは弱参照が使用されます。 つまり、オブジェクトはアクティブに保持されず、ガベージ コレクションの実行が可能になります。 そのため、クラスでメッセージを受信する必要がなくなった場合にのみ、メッセージのサブスクリプションを解除する必要があります。
eShopOnContainers モバイル アプリは、MessagingCenter
クラスを使って、疎結合されたコンポーネント間で通信します。 アプリでは、3 つのメッセージが定義されています。
- アイテムが買い物かごに追加されると、
CatalogViewModel
クラスによってAddProduct
メッセージが発行されます。 戻りでは、BasketViewModel
クラスがメッセージをサブスクライブし、応答で買い物かご内のアイテムの数をインクリメントします。 さらに、BasketViewModel
クラスはこのメッセージのサブスクライブ解除も行います。 Filter
メッセージは、ユーザーがカタログから表示されるアイテムにブランドまたは種類のフィルターを適用すると、CatalogViewModel
クラスによってパブリッシュされます。 戻りでは、CatalogView
クラスがメッセージをサブスクライブし、フィルター条件に一致するアイテムのみが表示されるように UI を更新します。ChangeTab
メッセージは、新しい注文の作成と送信が成功した後でCheckoutViewModel
がMainViewModel
に移動すると、MainViewModel
クラスによってパブリッシュされます。 戻りでは、MainView
クラスがメッセージをサブスクライブし、UI を更新して [マイ プロファイル] タブをアクティブにして、ユーザーの注文を表示します。
Note
MessagingCenter
クラスでは疎結合クラス間の通信が許可されますが、この問題に対する唯一のアーキテクチャ ソリューションは提供されません。 たとえば、ビュー モデルとビューの間の通信は、バインド エンジンとプロパティ変更通知を介して行うこともできます。 さらに、移動中にデータを渡すことで、2 つのビュー モデル間の通信を実現することもできます。
eShopOnContainers モバイル アプリでは、別のクラスで発生するアクションに応答して UI を更新するために MessagingCenter
が使われます。 そのため、メッセージは UI スレッドで発行され、サブスクライバーは同じスレッドでメッセージを受信します。
ヒント
UI の更新を実行するときは、UI スレッドにマーシャリングします。 UI を更新するためにバックグラウンド スレッドから送信されたメッセージが必要な場合は、Device.BeginInvokeOnMainThread
メソッドを呼び出してサブスクライバーの UI スレッドでメッセージを処理します。
MessagingCenter
について詳しく、MessagingCenter に関する記事をご覧ください。
メッセージの定義
MessagingCenter
メッセージは、メッセージを識別するために使用される文字列です。 次に示すコードは、eShopOnContainers モバイル アプリで定義されているメッセージの例です。
public class MessageKeys
{
// Add product to basket
public const string AddProduct = "AddProduct";
// Filter
public const string Filter = "Filter";
// Change selected Tab programmatically
public const string ChangeTab = "ChangeTab";
}
この例では、メッセージは定数を使用して定義されます。 この方法の利点は、コンパイル時の型の安全性とリファクタリングのサポートが提供されることです。
メッセージのパブリッシュ
パブリッシャーは、MessagingCenter.Send
オーバーロードの 1 つを使用してメッセージをサブスクライバーに通知します。 次のコード例は、AddProduct
メッセージの発行を示します。
MessagingCenter.Send(this, MessageKeys.AddProduct, catalogItem);
この例の Send
メソッドでは、3 つの引数を指定しています。
- 最初の引数では、送信者クラスを指定します。 送信者クラスは、メッセージを受信するサブスクライバーによって指定される必要があります。
- 2 番目の引数では、メッセージを指定します。
- 3 番目の引数では、サブスクライバーに送信するペイロード データを指定します。 この場合、ペイロード データは
CatalogItem
のインスタンスです。
Send
メソッドでは、ファイア アンド フォーゲット アプローチを使用して、メッセージとそのペイロード データが発行されます。 そのため、メッセージを受信するために登録されたサブスクライバーがない場合でも、メッセージが送信されます。 この状態では、送信されたメッセージは無視されます。
Note
MessagingCenter.Send
メソッドでは、汎用パラメーターを使用して、メッセージの配信方法を制御できます。 これにより、メッセージ ID を共有するものの、異なるペイロード データ型を送信する複数のメッセージを、異なるサブスクライバーが受信することができます。
メッセージのサブスクライブ
サブスクライバーは、MessagingCenter.Subscribe
オーバーロードのいずれかを使用して、メッセージを受信するように登録できます。 次に示すコードは、eShopOnContainers モバイル アプリで AddProduct
メッセージをサブスクライブして処理する方法の例です。
MessagingCenter.Subscribe<CatalogViewModel, CatalogItem>(
this, MessageKeys.AddProduct, async (sender, arg) =>
{
BadgeCount++;
await AddCatalogItemAsync(arg);
});
この例の Subscribe
メソッドは、AddProduct
メッセージをサブスクライブし、メッセージの受信に応答してコールバック デリゲートを実行します。 このコールバック デリゲートは、ラムダ式として指定され、UI を更新するコードを実行します。
ヒント
変更できないペイロード データの使用を検討してください。 受信したデータに複数のスレッドが同時にアクセスする可能性があるため、コールバック デリゲート内からはペイロード データを変更しないでください。 このシナリオでは、コンカレンシー エラーを回避するためにペイロード データを変更不可にする必要があります。
サブスクライバーは、パブリッシュされたメッセージのすべてのインスタンスを処理する必要はなく、これは Subscribe
メソッドで指定されたジェネリック型引数によって制御できます。 この例では、サブスクライバーは CatalogViewModel
クラスから送信された AddProduct
メッセージのみを受信し、そのペイロード データは CatalogItem
のインスタンスです。
メッセージのサブスクライブ解除
サブスクライバーは、受信する必要がなくなったメッセージのサブスクライブを解除することができます。 これは、次のコード例に示すように、MessagingCenter.Unsubscribe
オーバーロードのいずれかで実現されます。
MessagingCenter.Unsubscribe<CatalogViewModel, CatalogItem>(this, MessageKeys.AddProduct);
この例の Unsubscribe
メソッドの構文は、AddProduct
メッセージの受信をサブスクライブするときに指定した型引数を反映しています。
まとめ
Xamarin.FormsMessagingCenter
クラスでは、パブリッシュ/サブスクライブ パターンが実装され、オブジェクトと型の参照によってリンクしにくいコンポーネント間で、メッセージ ベースの通信を行うことができます。 このメカニズムにより、パブリッシャーとサブスクライバーは相互に参照することなく通信できるため、コンポーネント間の依存関係を減らすことができます。また、コンポーネントを個別に開発およびテストすることもできます。