sample: https://github.com/Microsoft/Appsample-Photosharing

 

PhotoSharingApp 에서는 ViewModel 객체를 생성할 때,
대부분의 경우 아래와 같은 형태로 ViewModel을 생성합니다.(CategoriesPage.xaml.cs)
---------------------------------------------------------------------
ServiceLocator.Current.GetInstance<CategoriesViewModel>(loadData);
---------------------------------------------------------------------
위와 같은 코드를 통해 return 되는 값은 CategoriesViewModel 객체입니다.
위 처럼 하여 ViewModel 객체를 얻을 수 있는 이유는,
UnityBootstrapper 내에서 UnityContainer 내부에 우리가 사용하게 될 ViewModel Type들을
Register 해두었기 때문입니다.(ViewModelRegistry.cs 에서 확인 가능)


그런데 이때 궁금한 점이 생겼습니다.
A page 가 존재할 때, A Page 입장에서는 아래와 같은 두 경우로 A Page에 올 수 있습니다.

1. 안드로이드 앱처럼, 현재 보던 A Page에서 B Page로 잠시 이동하였다가,Back으로 다시 돌아오는 경우.
2. B page에서 A Page로 바로 들어오는 경우. (새롭게 A Page가 생성되는 경우)

궁금한 점은, Back으로 돌아온 경우, 기존에 보던 A Page의 내용이 보존되어야 할 것입니다.
즉 다르게 말해서 ViewModel이 유지되고 있어야 함을 의미한다고 할 수 있습니다.

Back이 아닌, B page에서 A page로 바로 들어오는 경우는? 특별히 기존 데이터를 보존할 필요가 없는 경우이므로
새롭게 viewModel을 new 해주면 해결이 되겠지요..


그럼 여기서 어떻게 1,2번의 경우에 대해 처리를 해주고 있을까요?
맨 위로 돌아가면, 우리의 예제에서는 코드 비하인드에서
ServiceLocator.Current.GetInstance<CategoriesViewModel>(loadData);

위와 같이 viewModel을 얻고있는데, 어떻게 위의 1,2번 모두를 한줄로 알아서 관리가 되는걸 까요?


-----------------------------------------------------------------------
Unity 는 어떻게 위와 같은 상황을 관리하고 있는가.

정리

1. Unity를 쓰는 프로젝트에서는 ServiceLocator.current.GetInstance 를 통해 viewModel을 얻는다.
2. viewModel을 얻을 때, 내가 원하는 형태를(1,2번) flag 값을 전달하여 구분시켜 준다.(loadData)
3. UnityBootStrapper 내부에서 Container를 가지고 있는데, 해당 Container에
  우리 프로젝트에서 사용되는 ViewModel 들을 register 하게 된다.
  이 때, register를 하면서 내부적으로 getInstance가 호출 될 때,
 
  - viewModel을 매번 새롭게 생성해줄지
  - 내부적으로 singleton으로 관리하여 동일한 객체를 매번 return 할지.

  LifetimeManager 라는 객체(정책을 가지고있음)를 같이 등록하여 가능하게 만듭니다.
  (원하는데로 ViewModel , service 등등
  UnityContainer에 등록할 때 함께 등록한 Lifetimemanager가 가지고있는 정책에 맞게
  getInstance()가 동작합니다.)


정리한 내용이 좀 어렵지요?

간략히 요점만 정리하면,

우리는 UnityContainer에 viewModel, view, Service 따위를 등록해놓고
GetInstance(type) 형태로 전달받아 쓰게되는데 ,
등록할 때, LifeTimemanager를 함께 등록함으로써,
A page -> B page -> back 을 했을 때
viewModel이 singleton으로 관리되어
A Page의 ViewModel이 유지되도록 한다.

위와 같은 방법으로 기존 데이터가 유지될 수 있도록 합니다.

-------------------------------------------------------------------------------------

위 예제로 돌아가서 실제 코드를 보고 확인해볼까요?

(1단계)

-VieModelRegistry.cs-
Container.RegisterType<CategoriesViewModel>(new ContainerControlledLifetimeManager());

위와 같이 우리가 사용할 viewModel을 Container에 등록하면서,
기본적으로는 Container가 viewModel 객체를 Singleton으로 관리할 수 있도록
lifeTimemanager를 ContainerControlledLifeTimeManager와 함께 등록해놓습니다.

(2단계)

-CategoriesPage.xaml.cs-
OnNavigatedTo()
{
            var loadData = e.NavigationMode != NavigationMode.Back;
            _viewModel = ServiceLocator.Current.GetInstance<CategoriesViewModel>(loadData);
            DataContext = _viewModel;

            if (loadData)
            {
                await _viewModel.LoadState();
            }

}


위 처럼, Back으로 돌아온 경우, 최근에 해당 view의 viewModel을 그냥 쓰면 되기 때문에
loadData flag의 값은 false로 전달하게 됩니다.

반대로, Back이 아닌경우, loadData 값이 True가 되기 때문에
GetInstance() 메소드를 통해 리턴된 값은 새롭게 new 생성한 CategoriesVieModel 객체입니다.

텅텅 비어있는 새 viewModel 이기 때문에, 원하는 값을 채워놔야 되겠지요??

이 때는, 미리 viewModel에 override 해놓은 LoadState() 메소드를 호출하여,
원하는 값으로 viewModel 내용을 채워넣으면 됩니다.

해당 내용을 채워넣을 source는, OnNavigatedTo() 를 호출하는
NavigationFacade 쪽에서 만들어준 args 를 참고하여 내용을 채울수도 있습니다.

---------------------------------------------------------------------

여기까지 마.쏘의 uwp 샘플인 PhotoSharingApp 프로젝트와 함께보는
ServiceLocator의 객체 관리 정책과 view 전환에 따른 viewModel 유지 방법에 대해 알아보았습니다.

 

'WPF,UWP' 카테고리의 다른 글

[UWP] 개념 With PhotoSharing 샘플예제  (0) 2016.10.18
블로그 이미지

kuku_dass

,

UWP(Universal Windows Platform)

-       Lifecycle

n  Not running, running, suspended

u  몇 개의 앱을 삭제해야 하는 경우(Suspended state App이 너무 많은 리소스를 잡아 먹는 경우), 앱을 삭제 할 텐데, 이럴 때 사용자가 시스템이 종료시킨 앱에 돌아오려고 한다면?? 데이터를 잃을 것이다. 이러한 해프닝을 막기 위해선 아래의 두 Method 에서 useful data를 저장해주어야 한다.. The coding pattern for managing potential app terminations only requires you to save off any useful data from the current user session. Then, if the app gets terminated, you restore this data with custom code once the app is relaunched by the user.
(Read more at
https://blogs.windows.com/buildingapps/2016/04/28/the-lifecycle-of-a-uwp-app/#sL5cBJVCbLvUHK7p.99)

n  Application.Suspending : 일반적으로 앱이 terminated 수도 있다고 가정하고, 이를 대비하기 위해 하는 작업을 다루는 .

n  Application.Resuming : 보통은 아무것도 필요 없는 . 하지만??

u  좋은 UWP개발자라면? Suspending에서 큰 리소스를 deallocated 해줘야 할 것.

u  위에 처럼 Suspending 에서 리소스를 deallocated 했다면, Resuming 에서는 해제했던 리소스들을 reinitialize 해줘야 할 것이다.

n  Await 키워드 : await AppInitialization.DoInitializations();

u  Await이 붙으면 그 뒤에는 task가 위치한다. 만약 위의 코드 한줄이
private async Task<AppShell> Initialize()
안에 있다면, DoInitializations()가 실행이 되어 리턴될때까지(Task가 수행될 때 까지) Initialize() 메소드가 기다려 준다. .(http://nsinc.tistory.com/109)

n  GetDeferral()

u  var deferral = e.SuspendingOperation.GetDeferral();

//Async 작업.

deferral.Complete();

 

Deferral을 사용하면, 최대 5초까지 사용자가 필요로 하는 시간을 보장해준다.Deferral을 사용하는 목적은 결국 백그라운드 작업이 일찍 중지되지 않도록(최대5) 보장해주기 위해 사용.

백그라운드 작업에서 비동기 코드를 실행할 경우 백그라운드 작업은 deferral 사용해야 합니다. deferral 사용하지 않을 경우 비동기 메서드 호출이 완료되기 전에 Run 메서드가 완료되면 백그라운드 작업 프로세스가 예기치 않게 종료될 있습니다.

비동기 메서드를 호출하기 전에 Run 메서드에서 deferral 요청합니다. 비동기 메서드에서 액세스할 있도록 deferral 전역 변수에 저장합니다. 비동기 코드가 완료된 후에 deferral 완료를 선언합니다.

https://msdn.microsoft.com/ko-kr/windows/uwp/launch-resume/create-and-register-a-background-task

 

 

n  MVVM

u  View – ViewModel –Model

l  View : Page 객체이다.

n  View.cs : code-behind 코드 내에서, onNavigatedTo 내에서. 즉 이 page로 넘어올 때, ServiceLocator 에서 viewModel을 로드해온다. 그리고 set 해준다.

l  ViewModel : 상수, 커맨드, Property 들을 정의.

n  특징은 viewModel 생성자들은 자기의 view가 화면에 보여지게 하기 위해 파사드(façade) 가 필요하므로, 생성자에서 첫번째 인자로 façade를 전달받아, 해당 façade를 통해 화면전환을 진행 한다.

 

 

 

 

Unity: The Unity Container (Unity) is a lightweight, extensible dependency injection container with optional support for instance and type interception.

(https://msdn.microsoft.com/en-us/library/ff647202.aspx)

 ServiceLocator, UnityContainer, BootStrapper 등등 라이브러리 제공.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

메모장에 주절주절 한 것.

1. App.xaml.cs

 - OnActivated, OnLaunched -> Initialize -> Bootstrapper의 초기화

 

2. Bootstrapper.cs (The bootstrapper is responsible for the initialization of an application built using the Composite Application Library)

ServiceLocator, Registries, Container 생성 및 셋팅

 ViewRegistry Configure() 에서 NavigationFacade 에 각 Page ViewModel Facade에 등록.

 

 

*************************************************************

 

 

[ServiceLocator]

(http://www.c-sharpcorner.com/UploadFile/dacca2/service-locator-design-pattern/)

 

Generic type을 지원. 여러 타입의 Serivce <Service Type, Service object> 형식으로

Dictionary를 내부적으로 가지고 있고, 해당 Dictionary를 활용하여 GetService method를 지원 한다.

Dictionary Service Container 라 부른다.

 

 

[Bootstrapper]

Container Registries를 가지고 있고 App ServiceLocator 를 셋팅 해준다.

Container는 내부적으로 위에서 설명한 Dictionary를 가지고 있으며,

ServiceLocator를 생성할 때, Container를 만들어서 전달인자로 전달해준다.

 

그러면 ServiceLocator Conatiner는 우리가 원하는 Container로 설정이 되며,

그 다음 Registries을 생성 및 초기화 할 때, Locator가 들고있는 Conatiner를 전달해주어,

Registires 에서도 해당 Container <type, object> 형태로 저장을 한다.

 

결론: ServiceLocator가 들고있는 Container bootstrapper 가 들고있는 Container 이며,

해당 Container는 하나만 존재한다. 그리고 모든 Service(Facade 포함) 를 들고 있다.

 

[Container]

Bootstrapper에서 들고있고, Service를 관리.(type, object) dictionary.

 

[Registry] : 필요한 위치에 service, view , viewModel 등을 등록한다.

대표적으로 viewRegistry, viewModelRegistry, ServiceRegistry 가 있다.

 

- ServiceRegstry 에서는 App에 필요한 각종 Service Container에 등록하는 일을 한다.

IDialogService, NavigationFacade 등의 각 서비스를 Container에 등록하는 일을 한다.

 

*************************************************************

 

[AppShell]

현재까지 파악한 건, 가장 RootPage. 우리가 만드는 page 이다. App.xaml.cs 내에 있는 OnActivated, onLaunched

메소드 내에서, Window.Current.Content = new AppShell() 형태로 설정해줌으로써, 처음 화면이 시작된다.

AppShell 내에는 Frame이 있어서, 이 해당 Frame에다가 NavigationFacade가 화면을 설정해주면,

해당 Root 윈도우의 Frame에 원하는 View가 설정된다.

 

, AppShell은 하나의 Page이며, Root(main) page라 할 수있다.

그리고 root page에서 화면이 전환되길 원하는 영역을 AppShell.xaml에서 Frame 영역으로 정의해 둔다.

 

 

해결 된 질문 사항---------------------------------------------------------------------------------

 

Q) Frame Facade에 의해 제어 된 후,  AppShell(MainWindow)에 어떻게 화면이 뜨는지?

 

1. 우선 App.xaml.cs 에서 Window.current.Content = shell 이런 작업을 해준다.

content에 넣는 타입은 UIElement타입이고, 해당 코드를 통해 실제 현재 App Window의 화면이

결정된다.

2. Widnow.current.content = frame. 이런식. (new example을 만들면...)

3. PhotoSharing 프로젝트 내에, AppShell.xaml에는 <Frame x:name=frame /> 이 있어서 xaml내에 위치하고 있으며,

코드 비하인드내에 AppFrame 프로퍼티는 read-only이며, 해당 위 xaml에 있는 frame 객체를 return 한다.

즉 해당 App에서는 Facade AppShell.xaml Frame 객체를 가지고, navigate 한다.

 

핵심 : Tool에서 하나의 Frame을 사용하여 화면 제어. (지금까지 이해한 바로는...)

-> 테스트 결과 Page 클래스에 Frame객체를 들고있지만, Read only이고, AppXaml.cs에 있는

RootFrame에서 frame.Navigate(typeof(page1)) 을하면, page1.frame rootFrame frame으로 되는 듯.

 

App에서는 하나의 frame이 존재 -> Hashcode값이 모두 동일함을 확인.

 

Q) Facade Pattern ??

https://ko.wikipedia.org/wiki/%ED%8D%BC%EC%82%AC%EB%93%9C_%ED%8C%A8%ED%84%B4#C.23

 

 

(adapter pattern) : https://ko.wikipedia.org/wiki/%EC%96%B4%EB%8C%91%ED%84%B0_%ED%8C%A8%ED%84%B4

 

비슷하지만,

adapter pattern은 사용자가 기대하는 다른 인터페이스로 변환하는 패턴.

Facade pattern은 커다란 부분의 코드 부분을 간략화된 인터페이스로 사용자에게 제공하는 패턴.

 

 

 

Q) View ViewModel들이 어떻게 Datacontext로 셋이 되는가.. 기존 wpf에서처럼 viewmodel을 우리가 원할 때,

넣는 방법은?

 

1. SettingPage를 예로 들면 코드비하인드에서 ViewModel ServiceLocator.Current.GetInstance<SettingViewModel>(); 을 해서

얻는다.(이때 GetInstance는 확장메서드)

  type을 전달하면 ServiceLocator가 객체를 생성해서 리턴한다.

2. 일반적으로 page Code-behind내에있는 navigatedTo() 메소드 안에서, DataContext ServiceLocator GetInstance 확장메서드를 활용해서

ViewModel을 얻어와서 설정한다.

3. ViewModel을 만드는 다른 유형은 CropPage() 참고.

 

 - CropPage()의 생성자에서 우선 여러 paramter를 전달하여 viewModel을 만들어 놓는다.

NavigatedTo() 내에서는 전달받은 NavigationEventArgs 객체를 통해 이전 상태를 Load 할 수 있다. (viewModelBase 내에있는 ViewModel.LoadState() 사용)

(NavigatedTo() NavigationFacade에 구현된 NavigateToCropView() 내에서, Navigate 할 때,

ViewModel에 필요한 Args 를 같이 전달 해준다...)

 

 

Q) Facade를 이용하면, ViewModel도 싱글톤인가.?? GetInstance로 얻어오는데, 싱글톤인가..???

Facade에서 ViewModel을 생성하는것은 아니고, Facade에서는 약속된 Interface를 통해

클라이언트가 원하는 View navigate만 시켜준다. ViewModel View의 코드비하인드 내에서 알아서 만들어서쓰는데,

이때 Facade에서는 ViewModel에 필요한 args가 있다면, Navigate() parameter로 전달해줘서

code-behind에서 알아서 args를 참고해서 viewModel을 생성 혹은 갱신할 수 있도록 해준다.

 

 

위 질문에 대한 대답은 Facade view navigate만 해주며, viewModel에 필요한 args가 있다면,

Navigate()할 때, 같이 전달한다.

 

 

Q) Bootstrapper -> ServiceRegistry -> Container Facade RegisterType으로 추가하는데,

실제 NavigationFacade 객체를 생성하는곳은 어디인가..

 

일단 PhotoSharing 프로젝트에서는 마.소의 UnityServiceLocator를 쓰고, UnitiContainer를 쓴다.

UnityServiceLocator.Current.GetInstance()를 하면,

내부적으론 UnityContainer.Resolve() -> DoBuildUp() -> 내부에서 Type을 가지고 instance화 해서

Return 해준다.

 

Q) Design-time ViewModel ViewModel의 차이.

Design-time ViewModel : https://catelproject.atlassian.net/wiki/display/CTL/Design-time+view+models

( you can preview the UserControl or DataWindow implementations using example data.)

요지 : Design time  viewModel은 즉 테스용으로, 디자이너가 xaml을 직접 or blend 따위를 이용하여

작성한 후에, 실행한 화면을 보고싶을 때 사용될 viewModel. 또는 처음 실행했을 때,

값이 셋팅된 모습의 뷰를 보여주고자 할 때 사용한다.

 

IsDesignTimeCreatable flag 이용해서, 데모데이터를 보여주도록 설정 가능.

 

처음에 원하는 샘플용 데이터로 보여주고, 실제 유저가 사용할 때 Datacontext를 일반적인 VieModel

바꿔치기하면, 원래대로 사용.

 

미확인 질문 사항-------------------------------------------------------------------------------------------

 

 

Q)

ServiceLocator.Current.GetInstance<SettingsViewModel>() ;

 

이렇게하면 viewModel이 나오는데, 이땐 싱글톤은 아닐듯? 그때그때 type보고 새 객체를 생성해주지않을까..

근데 전달인자가 필요할땐 어떻게 GetInstance로 만들지??

 

전달인자가 있을때는, 글쎄...........

 

Q) GridView, FlipView (WelcomePage)

   HubSection (LeaderboardsPage)

   VisualState

 

  각각 Layout 공부하기.

 

 

 

블로그 이미지

kuku_dass

,