CommunityToolkit.Mvvm 연구 노트(4)——메신저

메신저 개요

WPF에 대해 어느 정도 이해하고 있다면 WPF의 명령이 ICommand 인터페이스를 구현하는 클래스라는 것을 알아야 합니다. 마찬가지로 이 기사의 제목은 메신저이지만 IMessenger 인터페이스로 시작됩니다. 메신저의 중국어 이름은 문자 그대로 '메신저'라고 부르는 것이 더 낫다고 생각합니다. 결국 메시지를 전달하는 것은 메신저의 능력입니다.

1 IMessenger 인터페이스

네임스페이스: Microsoft.Toolkit.Mvvm.Messaging
어셈블리: Microsoft.Toolkit.Mvvm.dll
패키지: Microsoft.Toolkit.Mvvm

IMessenger 인터페이스는 서로 다른 개체 간에 메시지를 교환하는 클래스가 가져야 하는 기능을 제공합니다. 이는 참조 유형에 대한 강력한 참조를 보유하지 않고도 프로그램의 서로 다른 모듈을 분리하는 데 매우 유용합니다 .

强引用:
当应用程序的代码能够访问到程序正在使用的对象时,GC(.NET平台的垃圾收集器)就不能回收该对象。
就称程序对该对象进行了强引用。
强引用和这有什么关系呢?
如果结合“解耦程序”来理解就会明了许多。
不关心代码能不能访问到该对象,我一样可以交换消息。

또한 지정된 채널(토큰으로 고유하게 식별됨)에 메시지를 보내고 다양한 메신저가 프로그램의 다른 부분에 머물도록 할 수도 있습니다.

IMessenger 기능을 사용하려면 먼저 다음과 같이 메시지 클래스를 정의하십시오.

// 定义一个登陆完成的消息
//(这里为了说明步骤而不是谈具体实现,所以只是定义了一个普通的class,
// 实际上它应该继承并实现IMessenger接口,不过MVVM工具包为你提供现成的类,后面会提到)
public sealed class LoginCompletedMessage {
    
     }
// sealed关键词会防止其它类继承该类
// 至于为什么加这个关键词,因为例子里加了

다음으로 메시지 수신자를 등록합니다.

Messenger.Default.Register<MyRecipientType, LoginCompletedMessage>(this, (r, m) =>
{
    
    
    // Handle the message here...
});

여기서 메시지 프로세서는 수신자(r, 수신자)와 메시지(m, 메시지)라는 두 개의 매개변수가 있는 람다 식입니다. (이 단어 는 Baidu에서 배울 수 있으며 람다 표현식에서 매우 일반적입니다.) 표현식이 현재 인스턴스를 캡처하면 클로저가 생성됩니다. 수신기는 수동 유형 변환 없이 핸들러에서 직접 액세스할 수 있도록 매개변수로 사용됩니다.

xx_handler(object sender, Args e)
{
     
     
	// 这类用法大家一定不陌生,需要你手动将object转换类型
	(sender as xxType).Method();
}

수신자를 매개변수로 사용하면 모든 확인이 빌드 시 수행되므로 코드의 중복이 줄어들고 안정성이 높아집니다.

핸들러가 수신자와 동일한 클래스에 정의된 경우 전용 멤버에 직접 액세스할 수도 있습니다. 이를 통해 메시지 처리기가 정적 메서드가 될 수 있으며, 이를 통해 C# 컴파일러는 일련의 추가 메모리 최적화(예: 불필요한 메모리 할당을 방지하기 위한 대리자 캐싱)를 수행할 수 있습니다. 마지막으로 필요할 때 다음과 같이 메시지를 보냅니다.

Messenger.Default.Send<LoginCompletedMessage>();

또한 현재 범위에서 올바른 서명이 있는 메서드를 사용할 수 있는 경우 메서드 그룹 구문을 사용하여 메시지를 수신할 때 호출할 메시지 처리기를 지정할 수 있습니다. 이는 등록 및 처리 논리를 별도로 유지하는 데 도움이 됩니다. 이전 예제를 바탕으로 다음 메서드를 사용하는 클래스를 생각해 보세요.

private static void Receive(MyRecipientType recipient, LoginCompletedMessage message)
{
    
    
    // Handle the message there
}

다음 코드가 등록됩니다.

Messenger.Default.Register(this, Receive);

C# 컴파일러는 표현식을 Register<TRecipient,TMessage>(IMessenger, TRecipient, MessageHandler<TRecipient,TMessage>) 와 호환되는 MessageHandler <TRecipient,TMessage> 인스턴스 로 자동 변환합니다 . 메소드의 여러 오버로드를 사용할 수 있는 경우 각각은 서로 다른 메시지 유형을 처리합니다. C# 컴파일러는 현재 메시지 클래스를 기반으로 적절한 유형을 자동으로 선택합니다. IRecipient<TMessage> 인터페이스를 사용하여 메시지 처리기를 명시적으로 등록할 수도 있습니다 . 이를 수행하려면 수신기는 인터페이스를 구현한 다음 RegisterAll(IMessenger, Object) 확장을 호출하기만 하면 됩니다. 이 확장은 수신기 유형에 의해 선언된 모든 핸들러를 자동으로 등록합니다. 물론 하나의 프로세서만 지원됩니다.

다음은 IMessenger 인터페이스입니다.
여기에 이미지 설명을 삽입하세요.
IMessenger에는 두 개의 파생 클래스가 있습니다.

  • Microsoft.Toolkit.Mvvm.Messaging.StrongReferenceMessenger
  • Microsoft.Toolkit.Mvvm.Messaging.WeakReferenceMessenger

이 두 클래스는 MVVM 도구 키트에서 제공하는 즉시 사용 가능한 두 가지 구현입니다(즉, IMessenger가 구현되었습니다).

  • 전자는 내부적으로 약한 참조를 사용하여 수신자에게 자동 메모리 관리를 제공합니다.
  • 후자는 강력한 참조를 사용하며 개발자가 수동으로 수신자를 구독 취소해야 합니다(수신자가 더 이상 필요하지 않은 경우). 그러나 그 대가로 더 나은 성능과 더 적은 메모리 사용량을 제공합니다.

상대적인 평균 API: IMessenger, WeakReferenceMessenger, StrongReferenceMessenger, IRecipient<TMessage>, MessageHandler<TRecipient, TMessage>, ObservableRecipient, RequestMessage<T>, AsyncRequestMessage<T>, CollectionRequestMessage<T>, AsyncCollectionRequestMessage<T>.

2 메신저 작동 방식

IMessenger 인터페이스를 구현하는 클래스 는 메시지 수신자(수신자)와 등록된 메시지 클래스 및 관련 메시지 처리기(처리기) 간의 링크를 유지 관리하는 일을 담당합니다. IMessenger 인스턴스를 사용하여 이 클래스의 메시지가 전송될 때마다 호출되는 메시지 핸들러를 사용하여 모든 객체를 지정된 메시지 클래스의 수신자로 등록할 수 있습니다 . 메시지는 특정 통신 채널(고유 토큰으로 식별됨)을 통해 전송될 수도 있으므로 여러 모듈이 충돌을 일으키지 않고 동일한 유형의 메시지를 교환할 수 있습니다. 토큰 없이 전송된 메시지는 기본 공유 채널을 사용합니다.

메시지를 등록하는 방법에는 두 가지가 있습니다. 하나는 IRecipient<TMessage>
인터페이스를 사용하는 것이고 , 다른 하나는 MessageHandler<TRecipient, TMessage> 대리자를 메시지 처리기로 사용하는 것입니다. 첫 번째 방법을 사용하면 메시지 처리기를 선언하는 모든 수신자를 자동으로 등록하는 RegisterAll 확장 메서드를 사용하여 모든 처리기를 등록할 수 있습니다. 메시지 프로세서로 보다 유연하거나 간단한 람다 표현식이 필요한 경우 후자가 더 적합합니다.
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.

WeakReferenceMessengerStrongReferenceMessenger는 모두 패키지에 내장된 스레드로부터 안전한 구현을 제공하는 Default 특성을 노출합니다 . 필요한 경우 여러 메신저 인스턴스를 생성할 수도 있습니다. 예를 들어 DI 서비스 공급자를 사용하여 다양한 인스턴스를 프로그램의 여러 모듈에 삽입할 수 있습니다(예: 여러 창이 동일한 프로세스에서 실행 중인 경우).


참고 : WeakReferenceMessenger는 사용하기가 더 간단하고 MvvmLight 라이브러리 의 메신저 동작과 더 잘 일치하므로 MVVM 도구 키트에서는 ObservableRecipient 의 기본 사용 유형
입니다 . StrongReferenceMessenger는 클래스의 생성자에 인스턴스를 전달하여 계속 사용할 수도 있습니다 .

3 메시지 보내기 및 받기

다음 코드를 고려해보세요.

// 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));

메시지 클래스가 현재 로그인한 사용자의 사용자 이름과 프로필 이미지가 포함된 제목 표시줄, 채팅 목록이 포함된 패널, 현재 채팅 패널이 포함된 다른 패널을 표시하는 간단한 메시징 프로그램(채팅 소프트웨어)에서 사용된다고 가정합니다. 메시지(하나가 선택됨) 이 세 부분이 각각 HeaderViewModel , ConversationsListViewModelConversationViewModel 에서 지원된다고 가정합니다 . 이 시나리오에서는 로그인 작업이 완료된 후 LoggedInUserChangedMessage 메시지가 HeaderViewModel 에 의해 전송되고 다른 두 개의 viewmodel은 메시지에 대한 핸들러를 등록합니다. 예를 들어 ConversationsListViewModel은 새 사용자의 대화 목록을 로드하고 ConversationViewModel은 현재 대화가 있으면 닫습니다.

IMessenger 인스턴스는 등록된 모든 수신자에게 메시지를 배포하는 일을 담당합니다. 수신자는 특정 유형의 메시지를 구독할 수 있습니다. 한 가지 더 주목해야 할 점은 상속된 메시지 클래스가 MVVM 툴킷에서 제공하는 기본 IMessenger 구현에 등록되지 않는다는 것입니다.

수신자가 더 이상 필요하지 않은 경우 등록을 취소하여 메시지 수신을 중단해야 합니다. 메시지 클래스, 등록 토큰 또는 수신자를 통해 등록을 취소할 수 있습니다.

// 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);

경고:
앞서 언급한 것처럼 WeakReferenceMessenger를 사용할 때 위의 등록 취소 작업은 엄격하게 요구되지 않습니다. 왜냐하면 약한 참조를 사용하여 수신기를 추적한다는 것은 사용되지 않는 수신기가 여전히 활성 메시지 핸들러를 가지고 있더라도 GC에 의해 여전히 정리된다는 의미이기 때문입니다 . 그러나 구독을 취소하는 것은 성능을 향상시킬 수 있는 좋은 습관입니다.

반면에 StrongReferenceMessenger 구현은 강력한 참조를 사용하여 등록된 수신자를 추적합니다. 이는 성능상의 이유로 수행됩니다. 즉, 메모리 누수를 방지하려면 등록된 각 수신기를 수동으로 등록 취소해야 합니다. 즉, 수신자가 등록되어 있는 한 사용 중인 StrongReferenceMessenger 인스턴스는 이에 대한 참조를 활성 상태로 유지하므로 GC가 인스턴스를 재활용하지 못하게 됩니다. 수동으로 처리할 수도 있고, 비활성화되면 기본적으로 모든 수신자의 메시지 등록을 자동으로 제거하는 ObservableRecipient에서 상속할 수도 있습니다.

IRecipient<TMessage> 인터페이스를 사용하여 메시지 처리기를 등록 할 수도 있습니다 . 이 경우 각 수신자는 주어진 메시지 클래스의 인터페이스를 구현하고 아래와 같이 메시지 수신 시 호출되는 Receiver(TMessage) 메서드 를 제공해야 합니다 .

// 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));

4 요청 메시지 사용

메신저 인스턴스의 또 다른 유용한 기능은 한 모듈에서 다른 모듈로 값을 요청하는 데 사용할 수 있다는 것입니다. 이를 위해 패키지에는 다음과 같이 사용되는 RequestMessage<T> 기본 클래스가 포함되어 있습니다.

// 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>();

RequestMessage<T> 클래스에는 LoggedInUserRequestMessage 의 세션을 포함하는 User 개체로 변환할 수 있게 해주는 암시적 변환기가 포함되어 있습니다. 또한 메시지에 대한 응답이 수신되었는지 확인하고 그렇지 않은 경우 예외를 발생시킵니다. 강제 응답 보장 없이 요청 메시지를 보내는 것도 가능합니다. 반환된 메시지를 로컬 변수에 저장하고 응답 값을 사용할 수 있는지 수동으로 확인하면 됩니다. 이렇게 하면 Send 메서드가 반환될 때 응답이 수신되지 않으면 자동 예외가 발생하지 않습니다.

네임스페이스에는
AsyncRequestMessage<T>, CollectionRequestMessage<T> 및 AsyncCollectionRequestMessage<T>와 같은 다른 시나리오에 대한 기본 요청 메시지도 포함됩니다 . 다음은 비동기 요청 메시지를 사용하는 예입니다.

// 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>();

Guess you like

Origin blog.csdn.net/BadAyase/article/details/125128698