다음을 통해 공유


느슨하게 결합된 구성 요소 간 통신

이 콘텐츠는 ‘.NET MAUI를 사용하는 엔터프라이즈 애플리케이션 패턴’ eBook에서 발췌한 것으로, .NET Docs에서 제공되거나 오프라인으로 읽을 수 있는 다운로드 가능한 무료 PDF로 제공됩니다.

Enterprise Application Patterns Using .NET MAUI eBook cover thumbnail.

게시-구독 패턴은 게시자가 구독자라고 하는 수신자 없이 메시지를 보내는 메시징 패턴입니다. 마찬가지로 구독자는 게시자를 알지 못한 채 특정 메시지를 수신 대기합니다.

.NET의 이벤트는 게시-구독 패턴을 구현하며 컨트롤 및 이를 포함하는 페이지와 같이 느슨한 결합이 필요하지 않은 경우 구성 요소 간 통신 계층에 대한 가장 간단한 방식입니다. 그러나 게시자 수명과 구독자 수명은 상호 개체 참조에 의해 결합되어 있으며, 구독자 유형에는 게시자 유형에 대한 참조가 있어야 합니다. 이로 인해 특히 정적 이벤트를 구독하는 수명이 짧은 개체 또는 수명이 긴 개체가 있는 경우 메모리 관리 문제가 발생할 수 있습니다. 이벤트 처리기가 제거되지 않은 경우, 구독자는 게시자에 있는 해당 구독자에 대한 참조에 의해 활성 상태로 유지되고, 이로 인해 구독자의 가비지 수집이 방지 또는 지연됩니다.

MVVM Toolkit Messenger 소개

MVVM Toolkit IMessenger 인터페이스는 게시-구독 패턴을 설명하여 개체 및 형식 참조별로 연결하기 불편한 구성 요소 간의 메시지 기반 통신을 허용합니다. 이 메커니즘을 사용하면 게시자와 구독자가 서로 직접 참조하지 않고도 통신할 수 있으므로 구성 요소 간의 종속성을 줄이는 동시에 구성 요소를 독립적으로 개발하고 테스트할 수 있습니다.

참고 항목

MVVM Toolkit Messenger는 CommunityToolkit.Mvvm 패키지의 일부입니다. 프로젝트에 패키지를 추가하는 방법에 대한 자세한 내용은 Microsoft 개발자 센터의 MVVM Toolkit 소개를 참조하세요.

Warning

.NET MAUI에는 더 이상 사용이 권장되지 않으며 MVVM Toolkit Messenger로 전환해야 하는 기본 제공 MessagingCenter 클래스가 포함되어 있습니다.

IMessenger 인터페이스는 멀티캐스트 게시-구독 기능을 허용합니다. 즉, 단일 메시지를 게시하는 여러 게시자가 있을 수 있으며 동일한 메시지를 수신 대기하는 여러 구독자가 있을 수 있습니다. 아래 이미지는 이 관계를 보여 줍니다.

Multicast publish-subscribe functionality.

CommunityToolkit.Mvvm 패키지와 함께 제공되는 IMessenger 인터페이스의 두 가지 구현이 있습니다. WeakReferenceMessenger는 메시지 구독자를 더 쉽게 정리할 수 있는 약한 참조를 사용합니다. 구독자의 수명 주기가 명확하게 정의되지 않은 경우 이 옵션을 사용하는 것이 좋습니다. StrongReferenceMessenger는 더 나은 성능을 제공하고 구독 수명을 더 명확하게 제어할 수 있는 강력한 참조를 사용합니다. 수명이 매우 제어되는 워크플로(예: 페이지의 OnAppearingOnDisappearing 메서드에 바인딩된 구독)가 있는 경우 성능이 중요하다면 StrongReferenceManager가 더 나은 옵션일 수 있습니다. 이러한 구현은 모두 WeakReferenceMessenger.Default 또는 StrongReferenceMessenger.Default를 참조하여 사용할 준비가 된 기본 구현과 함께 사용할 수 있습니다.

참고 항목

IMessenger 인터페이스는 느슨하게 결합된 클래스 간의 통신을 허용하지만 이 문제에 대한 유일한 아키텍처 솔루션을 제공하지는 않습니다. 예를 들어 뷰 모델과 뷰 간의 통신을 바인딩 엔진과 속성 변경 알림을 통해 수행할 수도 있습니다. 또한 탐색 중에 데이터를 전달하여 두 뷰 모델 간의 통신을 수행할 수도 있습니다.

eShopOnContainers 다중 플랫폼 앱은 WeakReferenceMessenger 클래스를 사용하여 느슨하게 결합된 구성 요소 간에 통신합니다. 이 앱은 AddProductMessage라는 단일 메시지를 정의합니다. 장바구니에 항목이 추가되면 AddProductMessageCatalogViewModel 클래스에 의해 게시됩니다. 그 대가로 CatalogView 클래스는 메시지를 구독하고 이를 사용하여 응답에서 애니메이션으로 제품 추가를 강조 표시합니다.

eShopOnContainers 다중 플랫폼 앱에서 WeakReferenceMessenger는 다른 클래스에서 발생하는 작업에 대한 응답으로 UI에서 업데이트하는 데 사용됩니다. 따라서 메시지는 클래스가 실행 중인 스레드에서 게시되며 구독자는 동일한 스레드에서 메시지를 수신합니다.

UI 업데이트를 수행할 때 UI 또는 주 스레드로 마샬링합니다. 이 스레드에서 사용자 인터페이스를 업데이트하지 않으면 애플리케이션이 충돌하거나 불안정해질 수 있습니다.

백그라운드 스레드에서 보낸 메시지가 UI를 업데이트해야 하는 경우 MainThread.BeginInvokeOnMainThread 메서드를 호출하여 구독자의 UI 스레드에서 메시지를 처리합니다.

Messenger에 대한 자세한 내용은 Microsoft 개발자 센터의 Messenger를 참조하세요.

메시지 정의

IMessenger 메시지는 사용자 지정 페이로드를 제공하는 사용자 지정 개체입니다. 다음 코드 예에서는 eShopOnContainers 다중 플랫폼 앱 내에 정의된 AddProductMessage 메시지를 보여 줍니다.

public class AddProductMessage : ValueChangedMessage<int>
{
    public AddProductMessage(int count) : base(count)
    {
    }
}

기본 클래스는 ValueChangedMessage<T>를 사용하여 정의됩니다. 여기서 T는 데이터를 전달하는 데 필요한 모든 형식이 될 수 있습니다. 메시지 게시자와 구독자 모두 특정 형식(예: AddProductMessage)의 메시지를 기대할 수 있습니다. 이를 통해 양 당사자가 메시징 계약에 동의했고 해당 계약과 함께 제공되는 데이터가 일관되게 유지되도록 할 수 있습니다. 또한 이 방식은 컴파일 시간 형식 안전성 및 리팩터링 지원을 제공합니다.

메시지 게시

메시지를 게시하려면 IMessenger.Send 메서드를 사용해야 합니다. 이는 WeakReferenceMessenger.Default.Send 또는 StrongReferenceMessenger.Default.Send를 통해 가장 일반적으로 액세스할 수 있습니다. 전송된 메시지는 모든 개체 형식이 될 수 있습니다. 다음 코드 예제에서는 AddProduct 메시지를 게시하는 방법을 보여 줍니다.

WeakReferenceMessenger.Default.Send(new Messages.AddProductMessage(BadgeCount));

이 예에서 Send 메서드는 다운스트림 구독자가 수신할 수 있도록 AddProductMessage 개체의 새 인스턴스를 제공하도록 지정합니다. 여러 명의 서로 다른 구독자가 잘못된 메시지를 수신하지 않고 동일한 형식의 메시지를 수신해야 하는 경우 사용하기 위해 추가적인 두 번째 토큰 매개 변수를 추가할 수 있습니다.

Send 메서드에서는 실행 후 제거(fire-and-forget) 방법을 사용하여 메시지와 페이로드를 게시합니다. 따라서 메시지를 수신하도록 등록된 구독자가 없는 경우에도 메시지가 전송됩니다. 이 경우 보낸 메시지는 무시됩니다.

메시지 구독

구독자는 IMessenger.Register<T> 오버로드 중 하나를 사용하여 메시지를 받도록 등록할 수 있습니다. 다음 코드 예제에서는 eShopOnContainers 다중 플랫폼 앱이 AddProductMessage 메시지를 구독하고 처리하는 방법을 보여 줍니다.

WeakReferenceMessenger.Default
    .Register<CatalogView, Messages.AddProductMessage>(
        this,
        async (recipient, message) =>
        {
            await recipient.Dispatcher.DispatchAsync(
                async () =>
                {
                    await recipient.badge.ScaleTo(1.2);
                    await recipient.badge.ScaleTo(1.0);
                });
        });

앞의 예제에서 Register 메서드는 AddProductMessage 메시지를 구독하고 메시지 수신에 대한 응답으로 콜백 대리자를 실행합니다. 람다 식으로 지정된 이 콜백 대리자는 UI를 업데이트하는 코드를 실행합니다.

참고 항목

대리자 내에서 해당 개체를 캡처하지 않으려면 콜백 대리자 내에서 this를 사용하지 마세요. 이는 성능 개선에 도움이 될 수 있습니다. 대신 recipient 매개 변수를 사용합니다.

페이로드 데이터를 제공하는 경우 여러 스레드가 수신된 데이터에 동시에 액세스할 수 있으므로 콜백 대리자 내에서 페이로드 데이터를 수정하지 마세요. 이 시나리오에서는 동시성 오류를 방지하기 위해 페이로드 데이터를 변경할 수 없도록 합니다.

메시지에서 구독 취소

구독자는 더 이상 수신하지 않을 메시지를 구독 취소할 수 있습니다. 이는 다음 코드 예에서 설명한 것처럼 IMessenger.Unregister 오버로드 중 하나를 사용하여 달성됩니다.

WeakReferenceMessenger.Default.Unregister<Messages.AddProductMessage>(this);

참고 항목

이 예에서는 WeakReferenceMessenger를 사용하면 사용되지 않은 개체를 가비지 수집할 수 있으므로 Unregister를 호출할 필요가 없습니다. StrongReferenceMessenger를 사용한 경우 더 이상 사용하지 않는 구독에 대해 Unregister를 호출하는 것이 좋습니다.

이 예에서 Unsubscribe 메서드 구문은 메시지의 형식 인수와 메시지를 수신 대기하는 받는 사람 개체를 지정합니다.

요약

MVVM Toolkit IMessenger 인터페이스는 게시-구독 패턴을 설명하여 개체 및 형식 참조별로 연결하기 불편한 구성 요소 간의 메시지 기반 통신을 허용합니다. 이 메커니즘을 사용하면 게시자와 구독자가 서로 참조하지 않고도 통신할 수 있으므로 구성 요소 간의 종속성을 줄이는 동시에 구성 요소를 독립적으로 개발하고 테스트할 수 있습니다.