Messenger

Interfejs IMessenger to kontrakt dla typów, które mogą służyć do wymiany komunikatów między różnymi obiektami. Może to być przydatne do oddzielenia różnych modułów aplikacji bez konieczności przechowywania silnych odwołań do typów, do których się odwołujesz. Istnieje również możliwość wysyłania wiadomości do określonych kanałów, jednoznacznie identyfikowanych przez token oraz do różnych komunikatorów w różnych sekcjach aplikacji. Zestaw narzędzi MVVM Udostępnia dwie implementacje gotowe do użycia: i StrongReferenceMessenger: WeakReferenceMessenger poprzedni używa słabych odwołań wewnętrznie, oferując automatyczne zarządzanie pamięcią dla adresatów, podczas gdy ten ostatni używa silnych odwołań i wymaga od deweloperów ręcznego anulowania subskrypcji adresatów, gdy nie są już potrzebne (więcej szczegółów na temat sposobu wyrejestrowania obsługi komunikatów można znaleźć poniżej), ale w zamian za to oferuje lepszą wydajność i znacznie mniejsze użycie pamięci.

Interfejsy API platformy:IMessenger, WeakReferenceMessenger, IRecipient<TMessage>StrongReferenceMessenger, MessageHandler<TRecipient, TMessage>, ObservableRecipient, RequestMessage<T>, AsyncRequestMessage<T>, CollectionRequestMessage<T>, . AsyncCollectionRequestMessage<T>

Jak to działa

Typy implementowane IMessenger są odpowiedzialne za utrzymywanie linków między adresatami (odbiornikami komunikatów) i ich zarejestrowanymi typami komunikatów z względnymi procedurami obsługi komunikatów. Każdy obiekt można zarejestrować jako adresat danego typu wiadomości przy użyciu programu obsługi komunikatów, który będzie wywoływany za każdym razem, gdy IMessenger wystąpienie jest używane do wysyłania komunikatu tego typu. Istnieje również możliwość wysyłania komunikatów za pośrednictwem określonych kanałów komunikacyjnych (z których każdy jest identyfikowany przez unikatowy token), dzięki czemu wiele modułów może wymieniać komunikaty tego samego typu bez powodowania konfliktów. Komunikaty wysyłane bez tokenu używają domyślnego udostępnionego kanału.

Istnieją dwa sposoby przeprowadzania rejestracji komunikatów: za pośrednictwem interfejsu IRecipient<TMessage> lub za pomocą delegata działającego MessageHandler<TRecipient, TMessage> jako procedura obsługi komunikatów. Pierwszy umożliwia zarejestrowanie wszystkich procedur obsługi za pomocą jednego wywołania RegisterAll rozszerzenia, które automatycznie rejestruje adresatów wszystkich zadeklarowanych programów obsługi komunikatów, podczas gdy ta ostatnia jest przydatna, gdy potrzebujesz większej elastyczności lub gdy chcesz użyć prostego wyrażenia lambda jako procedury obsługi komunikatów.

Zarówno WeakReferenceMessenger , jak i StrongReferenceMessenger uwidacznia Default właściwość, która oferuje wbudowaną implementację bezpieczną wątkowo w pakiecie. Istnieje również możliwość utworzenia wielu wystąpień komunikatora w razie potrzeby, na przykład jeśli inny z nich zostanie wstrzyknięty do innego modułu aplikacji (na przykład wiele okien uruchomionych w tym samym procesie).

Uwaga

WeakReferenceMessenger Ponieważ typ jest prostszy do użycia i pasuje do zachowania typu komunikatora z MvvmLight biblioteki, jest to domyślny typ używany przez ObservableRecipient typ zestawu narzędzi MVVM Toolkit. Można StrongReferenceType go nadal używać, przekazując wystąpienie do konstruktora tej klasy.

Wysyłanie i odbieranie komunikatów

Rozważ następujące źródła:

// Create a message
public class LoggedInUserChangedMessage : ValueChangedMessage<User>
{
    public LoggedInUserChangedMessage(User user) : base(user)
    {        
    }
}

// Register a message in some module
WeakReferenceMessenger.Default.Register<LoggedInUserChangedMessage>(this, (r, m) =>
{
    // Handle the message here, with r being the recipient and m being the
    // input message. Using the recipient passed as input makes it so that
    // the lambda expression doesn't capture "this", improving performance.
});

// Send a message from some other module
WeakReferenceMessenger.Default.Send(new LoggedInUserChangedMessage(user));

Wyobraźmy sobie, że ten typ komunikatu jest używany w prostej aplikacji do obsługi komunikatów, która wyświetla nagłówek z nazwą użytkownika i obrazem profilu aktualnie zarejestrowanego użytkownika, panelem z listą konwersacji i innym panelem z wiadomościami z bieżącej konwersacji, jeśli została wybrana. Załóżmy, że te trzy sekcje są obsługiwane odpowiednio przez HeaderViewModeltypy i ConversationsListViewModelConversationViewModel . W tym scenariuszu LoggedInUserChangedMessage komunikat może zostać wysłany przez polecenie po zakończeniu HeaderViewModel operacji logowania, a oba inne modele widoków mogą zarejestrować programy obsługi. Na przykład ConversationsListViewModel załaduje listę konwersacji dla nowego użytkownika i ConversationViewModel zamknie bieżącą konwersację, jeśli istnieje.

Wystąpienie IMessenger zajmuje się dostarczaniem wiadomości do wszystkich zarejestrowanych adresatów. Należy pamiętać, że odbiorca może subskrybować wiadomości określonego typu. Należy pamiętać, że dziedziczone typy komunikatów nie są zarejestrowane w domyślnych IMessenger implementacjach udostępnianych przez zestaw narzędzi MVVM Toolkit.

Gdy adresat nie jest już potrzebny, należy wyrejestrować go tak, aby przestał odbierać komunikaty. Możesz wyrejestrować według typu wiadomości, tokenu rejestracji lub adresata:

// Unregisters the recipient from a message type
WeakReferenceMessenger.Default.Unregister<LoggedInUserChangedMessage>(this);

// Unregisters the recipient from a message type in a specified channel
WeakReferenceMessenger.Default.Unregister<LoggedInUserChangedMessage, int>(this, 42);

// Unregister the recipient from all messages, across all channels
WeakReferenceMessenger.Default.UnregisterAll(this);

Ostrzeżenie

Jak wspomniano wcześniej, nie jest to absolutnie konieczne w przypadku używania WeakReferenceMessenger typu, ponieważ używa słabych odwołań do śledzenia adresatów, co oznacza, że nieużywane odbiorcy nadal będą kwalifikować się do odzyskiwania pamięci, mimo że nadal mają aktywne programy obsługi komunikatów. Nadal dobrym rozwiązaniem jest ich anulowanie, aby poprawić wydajność. Z drugiej strony implementacja StrongReferenceMessenger używa silnych odwołań do śledzenia zarejestrowanych adresatów. Jest to wykonywane ze względu na wydajność i oznacza to, że każdy zarejestrowany odbiorca powinien być ręcznie wyrejestrowany, aby uniknąć przecieków pamięci. Oznacza to, że o ile odbiorca zostanie zarejestrowany, StrongReferenceMessenger używane wystąpienie zachowa aktywne odwołanie, co uniemożliwi modułowi odśmiecywania pamięci możliwość zbierania tego wystąpienia. Można to zrobić ręcznie lub dziedziczyć z ObservableRecipientprogramu , który domyślnie automatycznie zajmuje się usuwaniem wszystkich rejestracji wiadomości dla adresata po dezaktywowaniu (zobacz dokumentację, aby uzyskać więcej informacji na ObservableRecipient ten temat).

Można również użyć interfejsu do rejestrowania IRecipient<TMessage> programów obsługi komunikatów. W takim przypadku każdy odbiorca będzie musiał zaimplementować interfejs dla danego typu komunikatu i podać metodę Receive(TMessage) , która będzie wywoływana podczas odbierania komunikatów, w następujący sposób:

// Create a message
public class MyRecipient : IRecipient<LoggedInUserChangedMessage>
{
    public void Receive(LoggedInUserChangedMessage message)
    {
        // Handle the message here...   
    }
}

// Register that specific message...
WeakReferenceMessenger.Default.Register<LoggedInUserChangedMessage>(this);

// ...or alternatively, register all declared handlers
WeakReferenceMessenger.Default.RegisterAll(this);

// Send a message from some other module
WeakReferenceMessenger.Default.Send(new LoggedInUserChangedMessage(user));

Używanie komunikatów żądań

Inną przydatną funkcją wystąpień komunikatora jest to, że mogą być one również używane do żądania wartości z modułu do innego. W tym celu pakiet zawiera klasę bazową RequestMessage<T> , której można użyć w następujący sposób:

// Create a message
public class LoggedInUserRequestMessage : RequestMessage<User>
{
}

// Register the receiver in a module
WeakReferenceMessenger.Default.Register<MyViewModel, LoggedInUserRequestMessage>(this, (r, m) =>
{
    // Assume that "CurrentUser" is a private member in our viewmodel.
    // As before, we're accessing it through the recipient passed as
    // input to the handler, to avoid capturing "this" in the delegate.
    m.Reply(r.CurrentUser);
});

// Request the value from another module
User user = WeakReferenceMessenger.Default.Send<LoggedInUserRequestMessage>();

Klasa RequestMessage<T> zawiera niejawny konwerter, który umożliwia konwersję z obiektu na LoggedInUserRequestMessage zawarty User w nim obiekt. Spowoduje to również sprawdzenie, czy odpowiedź została odebrana dla komunikatu, i zgłosi wyjątek, jeśli tak nie jest. Istnieje również możliwość wysyłania komunikatów żądań bez tej obowiązkowej gwarancji odpowiedzi: wystarczy przechowywać zwrócony komunikat w zmiennej lokalnej, a następnie ręcznie sprawdzić, czy wartość odpowiedzi jest dostępna, czy nie. Wykonanie tej czynności nie spowoduje wyzwolenia automatycznego wyjątku, jeśli odpowiedź nie zostanie odebrana po powrocie Send metody.

Ta sama przestrzeń nazw zawiera również podstawowy komunikat o żądaniach dla innych scenariuszy: AsyncRequestMessage<T>, CollectionRequestMessage<T> i AsyncCollectionRequestMessage<T>. Oto jak można użyć komunikatu żądania asynchronicznego:

// Create a message
public class LoggedInUserRequestMessage : AsyncRequestMessage<User>
{
}

// Register the receiver in a module
WeakReferenceMessenger.Default.Register<MyViewModel, LoggedInUserRequestMessage>(this, (r, m) =>
{
    m.Reply(r.GetCurrentUserAsync()); // We're replying with a Task<User>
});

// Request the value from another module (we can directly await on the request)
User user = await WeakReferenceMessenger.Default.Send<LoggedInUserRequestMessage>();

Przykłady

  • Zapoznaj się z przykładową aplikacją (dla wielu struktur interfejsu użytkownika), aby zobaczyć, jak działa zestaw narzędzi MVVM Toolkit.
  • Więcej przykładów można również znaleźć w testach jednostkowych.