Inversion of Control (IOC). 이미지 클릭시 출처로 이동

   대리자(델리게이트 Delegate)는 제어 역전(IOC, Invension of Control)의 방법 중 하나입니다. 자바의 스프링을 쓰신 분이라면 제어 역전이란 개념에 대해서 친숙하실 것 같습니다. 스프링에서는 Bean이라는 IOC 컨테이너를 사용하여 제어 역전을 구현하였습니다. 정확히는 IOC 중에서 의존성 주입(DI, Dependency Injection)을 사용합니다. 델리게이트도 이처럼 제어의 방향을 역전시키기 위한 방법 중 하나입니다. C++에서 이 Delegate를 구현하는 방법은 상당히 많습니다. C++ 11 이전에는 Boost 라이브러리의 Function과 Bind를 사용하여 구현하였고, C++ 11부터 이 구현이 표준으로 통합되었습니다. 또한 FastDelegate와 같이 기존의 Delegate 성능을 개선한 여러 다른 라이브러리도 있고, 이 게시글에서 사용하는 C++/WinRT의 전임자인 C++/CLI에서도 구현된 바가 있습니다.

 

   6일차에서 데이터 바인딩(링크)을 보기 전에 델리게이트에 대해 제대로 알지 못한다면 이해하기 어려운 부분이 상당히 많았을 거고, 저도 그런 사람중 하나였습니다. 이 게시글에서는 C++/WinRT에서 델리게이트가 어떻게 동작하고 또 어떻게 사용해야 하는지에 대해서 간략히 소개하겠습니다.

 

 

우리는 이미 델리게이트를 사용했다

   사실 우리는 이미 델리게이트를 사용했습니다. 그것도 무려 2일차부터입니다. 2일차에서는 Blank App으로 새 프로젝트를 만들면 자동으로 생성되는 코드를 확인했었는데요, 이 코드로 다시 돌아가 보겠습니다.

MainPage.xaml
MainPage.cpp

   델리게이트가 보이시나요? 사실 여기서 버튼에 Click 속성이 바로 이 델리게이트를 사용한 부분입니다. Click 속성을 주면 해당 메서드가 버튼에 전달됩니다. 메서드를 전달한다는 개념은 기존의 C++이라면 좀처럼 보기 어려울 것으로 예상됩니다. 마치 함수형 언어의 1급 객체(First-class citizen)을 보는 듯하기도 합니다. 언뜻 보기에는 MainPage가 ClickHandler를 실행해 버튼의 컨텐츠를 수정하는 것 처럼 보이지만, myButton의 Click에게 MainPage의 ClickHandler 메서드를 위임시켜 myButton이 클릭될 때 해당 메서드가 실행하게 되는 겁니다. 이렇게 제어의 주체가 MainPage가 아닌 myButton이 되기 때문에 화살표로 그려보면 방향이 반대가 됩니다. 이를 제어 역전이라고 부르고, 메서드를 위임하기 때문에 위임 패턴(Delegation Pattern)이라 부르며, 마이크로소프트는 대리자(Delegate)라고 칭하고 있습니다.

   

   일반적으로 C++/WinRT에서 델리게이트는 이벤트 처리를 사용할 때 사용합니다. 이벤트에 사용되는 델리게이트는 메서드를 위임할 때 winrt::event_token라는 구조체를 반환합니다. 이를 이용해 위임한 이벤트를 취소할 수가 있습니다. 위의 Click 이벤트는 ButtonBase에 구현되어 있는데 xaml에 선언적으로 작성할 수도, 명령적으로 작성할 수도 있습니다.

// MainPage.xaml
<Button x:Name="myButton" Click="ClickHandler">Click Me</Button>
// MainPage.h
void ClickHandler(
    winrt::Windows::Foundation::IInspectable const& sender,
    winrt::Windows::UI::Xaml::RoutedEventArgs const& args);

// MainPage.cpp
void MainPage::ClickHandler(
    IInspectable const& /* sender */,
    RoutedEventArgs const& /* args */)
{
    myButton().Content(box_value(L"Clicked"));
}

선언적으로 사용할 때는 위와 같이 xaml에 Click 속성과 그 값을 주면 됩니다. 그리고 선언은 h에, 구현은 cpp에 하면 되죠. 일반적으로 델리게이트는 idl에서 기술할 필요가 없습니다. 다음으로는 명령적으로 등록하는 방법입니다.

// MainPage.cpp
MainPage::MainPage()
{
    InitializeComponent();

    myButton().Click({ this, &MainPage::ClickHandler });
}
// MainPage.h
static void ClickHandler(
    winrt::Windows::Foundation::IInspectable const& sender,
    winrt::Windows::UI::Xaml::RoutedEventArgs const& args);

// MainPage.cpp
MainPage::MainPage()
{
    InitializeComponent();

    myButton().Click( MainPage::ClickHandler );
}
void MainPage::ClickHandler(
    IInspectable const& /* sender */,
    RoutedEventArgs const& /* args */) { ... }

여기에는 두 가지 방법이 있는데 첫번째는 그냥 위임하는 방법, 두 번째는 전역변수화한 메서드를 위임하는 방법입니다. 델리게이트로 멤버변수를 전달할 때는 꼭 현재 클래스를 가리키는 this 포인터와 같이 전달해야 합니다. 그래서 첫 번째 방법은 { this, &MainPage::ClickHandler }와 같이 기술합니다. 두 번째 방법은 ClickHandler를 전역 변수(static)화 하여 this를 생략합니다. 이러면 MainPage::ClickHandler가 MainPage의 멤버변수가 아니라 전역변수가 되기 때문에 this를 같이 전달할 필요가 없어집니다.

// MainPage.cpp
MainPage::MainPage()
{
    InitializeComponent();

    myButton().Click([this](IInspectable const& /* sender */, RoutedEventArgs const& /* args */)
    {
        myButton().Content(box_value(L"Clicked"));
    });
}
// MainPage.cpp
MainPage::MainPage()
{
    InitializeComponent();

    auto click_handler = [](IInspectable const& sender, RoutedEventArgs const& /* args */)
    {
        sender.as<winrt::Windows::UI::Xaml::Controls::Button>().Content(box_value(L"Clicked"));
    };
    myButton().Click(click_handler);
    AnotherButton().Click(click_handler);
}

아니면 위와 같이 간단한 동적의 경우 람다식을 사용해서 위임할 수도 있습니다. 위에는 람다식에 인자로 this를 넘겨 멤버 변수처럼 사용하였고, 아래는 this를 뺀 대신 sender를 이용하여 myButton에 접근할 수 있습니다.

 

참고

docs.microsoft.com/ko-kr/windows/uwp/cpp-and-winrt-apis/handle-events

 

C++/WinRT의 대리자를 사용한 이벤트 처리 - UWP applications

C++/WinRT를 사용하여 이벤트 처리 대리자를 등록하거나 취소하는 방법을 보여 줍니다.

docs.microsoft.com

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기