약한 이벤트 패턴
응용 프로그램에서는 이벤트 소스에 연결된 처리기가 처리기를 소스에 연결한 수신기 개체와 함께 삭제되지 않을 수 있습니다. 이로 인해 메모리 누수가 발생할 수 있습니다. Windows Presentation Foundation (WPF)에는 특정 이벤트에 전용 관리자 클래스를 제공하고 해당 이벤트의 수신기에 인터페이스를 구현함으로써 이러한 문제를 해결하는 데 사용할 수 있는 특수한 디자인 패턴이 도입되었습니다. 이 디자인 패턴을 약한 이벤트 패턴이라고 합니다.
약한 이벤트 패턴을 구현하는 이유
이벤트 수신은 메모리 누수를 야기할 수 있습니다. 일반적으로 이벤트는 소스의 이벤트에 처리기를 연결하는 언어별 구문을 사용하여 수신합니다. 예를 들어 C#에서는 이 구문이 source.SomeEvent += new SomeEventHandler(MyEventHandler)입니다.
이 방법을 사용하면 이벤트 소스에서 이벤트 수신기로의 강력한 참조가 만들어집니다. 일반적으로 수신기에 대한 이벤트 처리기를 연결하면 해당 수신기의 개체 수명이 소스의 개체 수명의 영향을 받게 됩니다. 단, 이벤트 처리기를 명시적으로 제거한 경우에는 예외입니다. 하지만 소스의 수명이 아니라 수신기가 응용 프로그램의 시각적 트리에 현재 속해 있는지 여부와 같은 다른 요소를 기준으로 수신기의 개체 수명을 제어하려는 경우가 있습니다. 소스의 개체 수명이 수신기의 개체 수명보다 긴 경우 일반적인 이벤트 패턴으로 인해 메모리 누수가 발생합니다. 즉, 수신기가 의도했던 것보다 오래 활성 상태를 유지합니다.
약한 이벤트 패턴은 이러한 메모리 누수 문제를 해결할 수 있도록 디자인되었습니다. 약한 이벤트 패턴은 이벤트에 대해 수신기를 등록해야 하지만 수신기가 자신의 등록 취소 시기를 명시적으로 알지 못하는 경우에 사용할 수 있습니다. 또한 소스의 개체 수명이 수신기의 적절한 개체 수명보다 긴 경우에도 약한 이벤트 패턴을 사용할 수 있는데 이 경우 적절한 수명은 사용자에 의해 결정됩니다. 약한 이벤트 패턴을 사용하면 수신기의 개체 수명 특성에 영향을 주지 않고 수신기가 이벤트에 대해 등록하여 이벤트를 수신할 수 있습니다. 수신기를 가비지 수집할 수 있는지 여부를 결정할 때는 소스로부터의 암시적 참조가 실제로는 어떤 영향도 주지 않습니다. 이러한 참조를 약한 참조라고 합니다. 약한 이벤트 패턴 및 관련 APIs도 이를 반영하여 이름이 지정되었습니다. 이 경우 수신기를 가비지 수집하거나 다른 방법을 통해 삭제할 수 있으며 소스는 삭제된 개체에 대한 수집할 수 없는 처리기 참조를 보유하지 않고도 계속 존재할 수 있습니다.
약한 이벤트 패턴을 구현해야 하는 사용자
약한 이벤트 패턴 구현은 주로 컨트롤 작성자와 관련된 기능입니다. 컨트롤 작성자는 컨트롤의 동작과 제약, 컨트롤이 삽입된 응용 프로그램에 컨트롤이 미치는 영향을 주로 담당합니다. 여기에는 컨트롤의 개체 수명 동작과 앞에서 설명한 메모리 누수 문제에 대한 처리 등이 포함됩니다.
본질적으로 약한 이벤트 패턴의 적용에 중점을 두는 시나리오도 있습니다. 이러한 시나리오 중 하나가 데이터 바인딩입니다. 데이터 바인딩에서는 일반적으로 소스 개체가 바인딩의 대상인 수신기 개체와 완전히 독립적입니다. WPF 데이터 바인딩의 대부분의 측면에는 이벤트 구현 방법에 있어 약한 이벤트 패턴이 이미 적용되어 있습니다.
약한 이벤트 패턴을 구현하는 방법
약한 이벤트 패턴 구현은 다음 세 가지 측면으로 이루어집니다.
WeakEventManager 클래스에서 관리자를 파생합니다.
소스에 대한 강력한 참조를 생성하지 않고, 약한 이벤트에 대한 수신기를 등록할 모든 클래스에 IWeakEventListener 인터페이스를 구현합니다.
수신기를 등록할 때 수신기가 약한 이벤트 패턴을 사용하도록 할 이벤트의 기본 add 및 remove 접근자를 사용하는 대신 이벤트에 대한 전용 WeakEventManager에서 AddListener 및 RemoveListener 구현을 사용합니다.
WeakEventManager
약한 이벤트 패턴을 구현하려면 일반적으로 이벤트와 1:1 관계로 관리자 클래스를 만듭니다. 예를 들어 Spin이라는 이벤트가 있으면 이 이벤트 전용의 약한 이벤트 관리자로 SpinEventManager 클래스를 만듭니다. 이 이벤트가 둘 이상의 클래스에 있고 각 클래스에서의 동작이 전반적으로 동일하며 같은 이벤트 데이터 형식을 공유하는 경우에는 각 이벤트에 동일한 관리자를 사용할 수 있습니다.
WeakEventManager 클래스에서 파생하는 경우 두 가상 메서드를 재정의하고 해당 이름이 가상 템플릿을 통해 관리되지는 않지만 존재해야 하는 몇 가지 다른 멤버를 노출합니다. 재정의는 WPF 인프라에서 이벤트 전달 모드를 시작하거나 종료하는 데 사용됩니다. 다른 멤버는 고유의 IWeakEventListener 구현이 WeakEventManager를 사용하여 이벤트에 수신기를 연결할 수 있도록 기능을 제공합니다.
WeakEventManager에서 파생하는 방법에 대한 자세한 내용은 WeakEventManager 참조 항목의 "상속자 참고 사항" 단원을 참조하십시오.
IWeakEventListener
IWeakEventListener 인터페이스에는 ReceiveWeakEvent라는 인터페이스 메서드가 하나 있습니다. ReceiveWeakEvent 구현은 해당 클래스에 존재하는 모든 이벤트 참조를 적절한 WeakEventManager로 전달하는 중앙 집중화된 구현이어야 합니다.
IWeakEventListener 인터페이스를 구현하는 방법에 대한 자세한 내용은 ReceiveWeakEvent 메서드 참조 항목의 "구현자 참고 사항" 단원을 참조하십시오.
수신기 연결
기본 이벤트인 ClockwiseSpin 이벤트(Spinner 형식으로 정의됨)를 사용한다고 가정해 보십시오. SpinListener라는 수신기 클래스가 있는데 이를 수신기로 사용하려면 약한 이벤트 패턴을 사용하는 대신 처리기를 연결하는 일반적인 방법을 따르는 경우 다음과 같이 += 연산자를 사용합니다.
spinnerInstance.ClockwiseSpin += new EventHandler(MyOnCWSpinHandler);
IWeakEventListener를 구현하는 클래스가 있고 구현에 ClockwiseSpin 이벤트 및 해당 관리자를 포함해야 하는 경우 약한 이벤트 패턴을 사용하는 구문은 다음과 같습니다.
ClockwiseSpinEventManager.AddListener(spinnerInstance, this);
해당 이벤트에 대한 처리 논리를 기본 대리자 기반 처리기로 지정하는 대신 클래스에 구현한 ReceiveWeakEvent 중 하나에서 지정합니다.
외부 이벤트에 대해 WeakEvent 패턴 구현
약한 이벤트 패턴이 지닌 한 가지 흥미로운 점은 코드베이스에 포함되지 않은 이벤트에 대해 이 패턴을 구현할 수 있다는 것입니다. 소스 관점에서는 처리기가 해당 이벤트에 연결되는 방법이 같으며 처리기가 WeakEventManager를 통해 제어됩니다. 이벤트에 대해 WeakEventManager를 정의한 다음 해당 이벤트를 수신하기 위해 약한 이벤트 패턴을 사용할 가능성이 있는 모든 수신기에서 이벤트를 ReceiveWeakEvent 논리의 일부로 포함하기만 하면 됩니다.