Messenger
A interface IMessenger
é um contrato para tipos que podem ser usados para trocar mensagens entre objetos diferentes. Isso pode ser útil para desacoplar diferentes módulos de um aplicativo sem precisar manter referências fortes aos tipos que estão sendo referenciados. Também é possível enviar mensagens para canais específicos, identificados exclusivamente por um token e ter mensageiros diferentes em diferentes seções de um aplicativo. O Kit de Ferramentas MVVM fornece duas implementações prontas: WeakReferenceMessenger
e StrongReferenceMessenger
: o primeiro usa referências fracas internamente, oferecendo gerenciamento automático de memória para destinatários, enquanto este usa referências fortes e exige que os desenvolvedores cancelem manualmente seus destinatários quando eles não são mais necessários (mais detalhes sobre como cancelar o registro de manipuladores de mensagens podem ser encontrados abaixo), mas em troca disso oferece melhor desempenho e muito menos uso de memória.
APIs de plataforma:
IMessenger
,WeakReferenceMessenger
,StrongReferenceMessenger
,IRecipient<TMessage>
,MessageHandler<TRecipient, TMessage>
,ObservableRecipient
,RequestMessage<T>
,AsyncRequestMessage<T>
,CollectionRequestMessage<T>
,AsyncCollectionRequestMessage<T>
.
Como ele funciona
Os tipos que implementam o IMessenger
são responsáveis por manter links entre destinatários (receptores de mensagens) e seus tipos de mensagens registrados, com manipuladores de mensagens relativos. Qualquer objeto pode ser registrado como um destinatário para um determinado tipo de mensagem usando um manipulador de mensagens, que será invocado sempre que a instância IMessenger
for usada para enviar uma mensagem desse tipo. Também é possível enviar mensagens por meio de canais de comunicação específicos (cada um identificado por um token exclusivo), para que vários módulos possam trocar mensagens do mesmo tipo sem causar conflitos. As mensagens enviadas sem um token usam o canal compartilhado padrão.
Há duas maneiras de executar o registro de mensagens: por meio da interface IRecipient<TMessage>
ou usando um representante MessageHandler<TRecipient, TMessage>
atuando como manipulador de mensagens. O primeiro permite registrar todos os manipuladores com uma única chamada para a extensão RegisterAll
, que registra automaticamente os destinatários de todos os manipuladores de mensagens declarados, enquanto esse último é útil quando você precisa de mais flexibilidade ou quando deseja usar uma expressão lambda simples como um manipulador de mensagens.
Tanto WeakReferenceMessenger
quanto StrongReferenceMessenger
também expõem uma propriedade Default
que oferece uma implementação thread-safe interna no pacote. Também é possível criar várias instâncias do messenger, se necessário, por exemplo, se uma diferente for injetada com um provedor de serviços DI em um módulo diferente do aplicativo (por exemplo, várias janelas em execução no mesmo processo).
Observação
Como o tipo WeakReferenceMessenger
é mais simples de usar e corresponde ao comportamento do tipo de mensageiro da biblioteca MvvmLight
, é ele o tipo padrão usado pelo tipo ObservableRecipient
no Kit de Ferramentas MVVM. O StrongReferenceType
ainda pode ser usado passando uma instância para o construtor dessa classe.
Enviar e receber mensagens
Considere o seguinte:
// 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));
Vamos imaginar esse tipo de mensagem sendo usado em um aplicativo de mensagens simples, que exibe um cabeçalho com o nome de usuário e a imagem de perfil do usuário conectado no momento, um painel com uma lista de conversas e outro painel com mensagens da conversa atual, se um estiver selecionado. Digamos que essas três seções sejam compatíveis com os tipos HeaderViewModel
, ConversationsListViewModel
e ConversationViewModel
respectivamente. Nesse cenário, a mensagem LoggedInUserChangedMessage
pode ser enviada pelo HeaderViewModel
após a conclusão de uma operação de logon, e ambos os outros viewmodels podem registrar manipuladores para ela. Por exemplo, ConversationsListViewModel
carregará a lista de conversas para o novo usuário e ConversationViewModel
fechará apenas a conversa atual, se houver uma.
A instância IMessenger
cuida do fornecimento de mensagens a todos os destinatários registrados. Observe que um destinatário pode assinar mensagens de um tipo específico. Observe que os tipos de mensagem herdados não são registrados nas implementações IMessenger
padrão fornecidas pelo Kit de Ferramentas MVVM.
Quando um destinatário não for mais necessário, você deverá cancelar seu registro para que ele pare de receber mensagens. Você pode cancelar o registro por tipo de mensagem, por token de registro ou por destinatário:
// 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);
Aviso
Conforme mencionado antes, isso não é estritamente necessário ao usar o tipo WeakReferenceMessenger
, pois ele usa referências fracas para rastrear destinatários, o que significa que os destinatários não utilizados ainda serão elegíveis para coleta de lixo, mesmo que ainda tenham manipuladores de mensagens ativos. No entanto, ainda é uma boa prática cancelar suas inscrições para melhorar o desempenho. Por outro lado, a implementação StrongReferenceMessenger
usa referências fortes para rastrear os destinatários registrados. Isso é feito por motivos de desempenho e significa que cada destinatário registrado deve ser registrado manualmente para evitar perdas de memória. Ou seja, enquanto um destinatário for registrado, a instância StrongReferenceMessenger
em uso manterá uma referência ativa a ele, o que impedirá que o coletor de lixo possa coletar essa instância. Você pode lidar com isso manualmente ou herdar do ObservableRecipient
, o que, por padrão, cuida automaticamente da remoção de todos os registros de mensagens para o destinatário quando ele é desativado (consulte a documentação sobre ObservableRecipient
para obter mais informações sobre isso).
Também é possível usar a interface IRecipient<TMessage>
para registrar manipuladores de mensagens. Nesse caso, cada destinatário precisará implementar a interface para um determinado tipo de mensagem e fornecer um método Receive(TMessage)
que será invocado ao receber mensagens, da seguinte maneira:
// 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));
Usando mensagens de solicitação
Outro recurso útil das instâncias do messenger é que elas também podem ser usadas para solicitar valores de um módulo para outro. Para fazer isso, o pacote inclui uma classe RequestMessage<T>
base, que pode ser usada da seguinte maneira:
// 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>();
A classe RequestMessage<T>
inclui um conversor implícito que possibilita a conversão de um LoggedInUserRequestMessage
para seu objeto contido User
. Isso também verificará se uma resposta foi recebida para a mensagem e gerará uma exceção se esse não for o caso. Também é possível enviar mensagens de solicitação sem essa garantia de resposta obrigatória: basta armazenar a mensagem retornada em uma variável local e verificar manualmente se um valor de resposta está disponível ou não. Isso não disparará a exceção automática se uma resposta não for recebida quando o método Send
retornar.
O mesmo namespace também inclui a mensagem de solicitações base para outros cenários: AsyncRequestMessage<T>
,CollectionRequestMessage<T>
e AsyncCollectionRequestMessage<T>
.
Veja como você pode usar uma mensagem de solicitação assíncrona:
// 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>();
Exemplos
- Confira o aplicativo de exemplo (para várias estruturas de interface do usuário) para ver o Kit de Ferramentas MVVM em ação.
- Você também pode encontrar mais exemplos nos testes de unidade.