티스토리 뷰

반응형

안녕하세요 Pingu 입니다.

 

오늘은 Xcode에서 iOS App 템플릿으로 프로젝트를 만들면 항상 만들어진 AppDelegate.swift, SceneDelegate.swift에 대해 알아보려고 합니다. 얘들은 뭐 하는 애들이길래 항상 만들어지는 건지 궁금했었는데 한 번 알아보겠습니다.

 

오늘 알아볼 SceneDelegate.swift입니다.

일단 이 녀석은 iOS13 버전 이전에는 존재하지 않던 코드입니다.

근데 iOS13부터 iPad에서 multi window를 지원하게 되면서 scene delegate가 추가되었다고 합니다.

위와 같이 multi window를 지원하게 되면서 scenedelegate가 필요하게 되었다고 하네요.

 

이에 대한 WWDC 발표도 있으니 한 번 참고해보시면 좋을 듯합니다.

developer.apple.com/videos/play/wwdc2019/258/

 

Architecting Your App for Multiple Windows - WWDC 2019 - Videos - Apple Developer

Dive into the details about what it means to support multitasking in iOS 13. Understand how previous best practices fit together with new...

developer.apple.com

이번 글은 이 영상을 정리하는 형식으로 써보려고 합니다.

 

우선 iOS 13에서부터 multiple Window를 지원하게 되면서 앱의 Lifecycle에 변경 사항이 있다고 합니다. 우선 iOS 12까지의 App Delegate의 역할을 보면...

iOS 12까지 App Delegate는 2가지 중요한 역할을 했었습니다. 하나는 앱의 프로세스 수준을 알리는 것이죠. 그래서 시스템이 앱을 실행하거나 종료할 때 App Delegate에게 알려야 했습니다. 다른 하나의 역할은 앱이 현재 UI State를 알 수 있도록 하는 역할이었습니다.

이는 iOS 12까지는 괜찮았던 게 하나의 앱은 하나의 프로세스와 하나의 UI만 사용했기 때문이에요.

근데 iOS 13부터는 앱들이 하나의 공유 프로세스를 사용하지만 여러 개의 UI, scene session을 가질 수 있기 때문에 App Delegate의 역할을 조금 바꿀 필요가 있습니다. 즉 App Delegate는 실행 이벤트와 같은 LifeCycle에는 책임이 있지만 더 이상 UI LifeCycle에는 책임이 없다는 것이죠. 

이를 iOS 13부터는 UIScene Delegate가 책임 지게 됩니다. 즉 개발자들은 App Delegate에서 수행하던 UI 관련 작업들을 Scene Delegate로 이전해야 해요.

만약 iOS 13에서 앱이 새로운 Scene LifeCycle을 채택하면 UIKit은 App Delegate의 UI 상태에 관여하는 메서드 호출을 하지 않습니다. 대신 Scene Delegate에서 메서드를 호출합니다. 이는 1:1 매핑이 되어 있기 때문에 사용하기는 쉽다고 하네요! 물론 이미 만들어진 앱에 multiple window를 지원하고 싶지 않다면 이를 사용하지 않으면 됩니다. 그럼 똑같이 사용할 수 있는 것이죠!

앱 델리게이트는 지금까지 알아본 책임에 더해 추가적인 책임이 있는데요, 바로 Session Lifecycle입니다. 즉 시스템은 이제 새로운 scene session이 생성되거나 기존 scene session이 삭제될 때 앱 델리게이트에게 알려야 합니다.

 

그럼 이제 앱의 Lifecycle에 대해 좀 더 구체적으로 알아보겠습니다!

위의 아이패드에서 파란색 앱을 실행하고 싶어서 눌렀다고 가정해보겠습니다.

그럼 App delegate에서 didFinishLaunching을 호출합니다. 여기까지는 iOS 12와 동일해요. 또한 이 타이밍에 UI가 아닌 일회성 설정을 하면 좋다고 합니다.

그런 뒤 시스템이 바로 scene session을 생성합니다. 근데 scene을 만들려고 하면 어떻게 생겼는지를 알아야 하잖아요? 그래서 시스템은 UIScene Configuration(구성)에 대해 앱에 물어봅니다.

이런 scene Configuration은 scene delegate, storyboard 등으로 알 수 있죠. 이런 구성은 info.plist에서 정적으로 정의하거나 코드로 동적으로 정의할 수 있습니다. 이렇게 정의할 수 있는 방법이 많다는 것은 알맞은 Configuration을 선택할 기회를 주기 위함이라고 합니다.

그럼 이제 이렇게 정의한 scene Configuration에는 main sceneConfiguration과 accessory scene Configuration을 가질 수 있습니다.

따라서 개발자는 위의 코드처럼 옵션 매개변수들을 보고 이를 컨텍스트로 사용해서 올바른 scene Configuration을 선택해줘야 해요. 이러한 것들을 info.plist에서 정의하면 간단하게 할 수 있다고 합니다. 위에 코드에 보시면 UISceneConfiguration 함수에 name 매개변수가 보이시나요? 저기에 들어오는 이름의 session을 전달하면 된다고 합니다.

그렇게 하면 앱도 실행되고 scene session도 가질 수 있는데 UI를 볼 수 없습니다. 여기가 바로 Scene delegate가 scene session를 연결하는 곳입니다. 이 때는 또 어떤 일을 해야 하는지 알아보겠습니다.

자주 보던 Scene delegate네요. 여기서 새로 지정된 UI Window를 설정합니다. scene 함수에 매개변수로 입력된 scene을 사용해서 말이죠. 여기서 중요한 것은 윈도우를 만들었을 땐 User Activity(사용자 활동)이나 state Restoration activity(상태 복원활동)도 확인해야 합니다. 이에 대해서는 잠시 후에 알아보도록 하겠습니다!

여기까지 하면 앱에 UI도 보이게 됩니다! 그럼 만약 사용자가 홈 화면으로 가기 위해 홈버튼을 누르거나 위로 스와이프 하면 어떻게 될까요?

이런 상황에서는 willResignActive, didEnterBackground 메서드가 호출되어 active 상태를 포기하고 scene을 background로 보냅니다. 그러다가 어느 시점에서 didDisconnect가 호출되어 scene의 연결이 해제될 수 있습니다. 이게 무슨 말일 까요?

시스템은 사용하지도 않는 scene에게 자원을 주고 싶지 않기 때문에 background에 있는 scene을 메모리에서 해제해버릴 수도 있다는 말입니다. 이렇게 scene이 해제되면 이를 보유한 모든 윈도우 계층, 뷰 계층도 해제되게 됩니다. 그런데 scene이 나중에 다시 연결되거나 반환될 수도 있는데 이렇게 메모리에서 해제해버리면 괜찮을까요? scene이 가지고 있던 사용자 데이터나 상태를 영구적으로 삭제하면 안 될 것 같습니다. 이를 다시 자세히 알아보겠습니다.

위에 아이패드에 있는 창을 아시나요? 앱들을 선택하는 창.. 음 여기서는 app switcher라고 부르더라고요. 어쨌든 여기서 위로 스와이프 하면 앱을 사용자가 직접 제거할 수 있습니다. 이 상황을 살펴보겠습니다.

위로 스와이프 하여 앱을 직접 제거하면 App delegate의 didDiscardSceneSession이 호출됩니다. 

didDiscardSceneSession이 호출되면 어떤 일이 발생할까요. 예를 들어 문서 편집 앱을 사용하다가 직접 앱을 해제했다고 한다면 저장되지 않은 문서나 상태를 위임할 기회를 준다고 합니다. 따라서 사용자가 앱이 실행되지 않는 동안 UIScene을 직접 해제할 수 있게 된다고 하네요. 프로세스가 동작하지 않는 다면 시스템은 폐기된 session을 추적하고 다음에 다시 실행할 때 이를 호출한다고 합니다.

...?

 

"This finally gives us an opportunity to actually permanently delegate any user state or data associated with the scene such as an unsaved draft in a text editing app.

 

Now, it's also possible that one of your users removed one or more UIScenes from the switcher by swiping up while your actual app process was not running.

 

If your process was not running, the system will keep track of the discarded sessions and call this shortly after your application's next launch."

 

원래는 이런 말인데.. 해석이 어렵네요..

그러니까 앱을 제거하더라도 session은 위임하고 있게 되고 이 때문에 다음에 다시 실행할 때 이를 다시 didDiscardSceneSession을 호출한다는 말일까요? 음.. 확실히는 모르겠지만 설명상 앱을 메모리에서 해제한다고 해도 session은 기억을 해뒀다가 나중에 다시 실행할 때 사용할 수 있다는 말 같네요.

 

일단 넘어가도록 하겠습니다.

이제는 아키텍처에 대해 알아보겠습니다.

아까 잠시 후에 알아본다고 했던 state Restoration(상태 복원)에 대해 알아보겠습니다. iOS 13에서 앱은 scene 기반 상태 복원을 구현하는 것이 중요하다고 합니다.

위와 같이 app switcher가 있다고 봅시다. 현재 문서 앱이 있는데 여행을 계획 중입니다. 문서 session은 총 4개가 있네요.

근데 저는 Packing List, Agenda에 집중하고 싶습니다. 따라서 어느 시점에 Road Trip, Attendees는 시스템에 의해 연결 해제된 것을 볼 수 있죠. 만약 이 앱이 상태 복원을 구현하지 않은 앱이라면 Road Trip, Attendees로 돌아갈 때 이전 상태로 돌아갈 수 없을 거예요. 즉 작성하고 있던 문서들이 모두 사라지는... 무서운 상황이 벌어지는 것이죠. 이를 어떻게 해결할 수 있을까요?

iOS 13은 새로운 scene-based 상태 복원 API를 가지고 있고 이를 사용하기는 간단하다고 하네요.(다 간단하다고 하는 듯한 느낌 ㅎㅎ;;)

이는 상태를 인코딩해서 창을 다시 만들 수 있다고 합니다. 이는 모두 NSUser Activity를 기반으로 한다고 해요. 따라서 앱은 스포트 라이트 검색, handoff와 같은 기능들을 활용하는 동안 동일한 활동을 사용하여 앱의 상태를 인코딩할 수 있다고 합니다. iOS 13에서 시스템에 반환하는 상태 복원 정보는 나머지 앱의 데이터 보호 등급과 일치한다고 합니다. 이러한 과정을 코드에서는 어떻게 사용할까요?

Scene Delegate에서 Scene에 대한 stateRestorationActivity를 구현한 뒤 현재 창에서 가장 활동적인 사용자 활동을 찾는 메서드(fetchCurrentUserActivity)를 호출합니다. 그리고 이를 반환하면 됩니다. 

잠시 후 scene이 foreground로 다시 들어와서 연결되면 session에 stateRestorationActivity가 있는지 확인하고 있다면 이를 사용하고 없다면 그냥 새로운 윈도우를 만들면 됩니다. 즉 사용자는 background에서 scene의 연결이 끊기는 것을 알 수 없습니다.

이제 마지막으로 앱의 scene을 동기화 상태로 유지하는 방법에 대해 알아보겠습니다.

이제 새로운 채팅앱을 사용한다고 가정할 것이고 이 앱은 iOS 13의 multiple window를 지원한다고 보겠습니다. 방금까지 Giovanni와 채팅했는데 서로 다른 두 개의 뷰 컨트롤러와 다른 scene에서 동일한 대화를 보고 있는 것을 알 수 있죠? 만약 이 상태에서 왼쪽 창에서 Giovanni에게 메시지를 보내면..

왼쪽 뷰 컨트롤러에서만 업데이트되었죠. 분명 같은 화면을 보고 있는데 말이에요. 왜 이런 현상이 발생할까요? 

위의 그림과 같이 iOS의 많은 앱이 위와 같은 방식으로 구성되어 있기 때문입니다. 뷰 컨트롤러가 이벤트를 수신하는 방식이죠. 아까 채팅 앱에서는 메시지를 보낸다면 이벤트를 수신하게 됐을 거예요. 그러면 뷰 컨트롤러는 자신의 UI를 업데이트하죠. 그런 뒤 모델, 모델 컨트롤러에게 이를 알릴 거예요. 이는 앱마다 하나의 UI만 존재했을 때는 괜찮은 방법이었습니다. 위의 예에서만 봐도 UI가 2개가 되니까 바로 이상해지죠?

즉 위의 예에서 오른쪽 뷰 컨트롤러는 이벤트가 발생하지 않았기 때문에 자신의 UI를 변경하지 않았던 거죠.

이를 해결하는 방법은 뷰 컨트롤러가 이벤트를 수신하면 모델 컨트롤러에게 알리는 겁니다. 이렇게 되면 모델 컨트롤러가 연결된 뷰 컨트롤러에게 새로운 데이터로 업데이트하라고 알릴 수 있죠.

 

이를 구현하는 데는 많은 방법이 있습니다. delegate를 사용하거나 notification을 사용하거나 Swift Combine Framework도 있다고 하네요. 하지만 이번에는 notification으로 구현해보겠습니다.

위의 코드는 이를 반영하지 않은 코드, 즉 2개의 UI가 있더라도 하나의 뷰 컨트롤러만 수정되던 상황의 코드입니다. 위에 있는 didEnterMessage 메서드는 메시지를 보낼 때 보내기 버튼을 누르면 호출되는 메서드입니다. 메시지 모델 객체를 만들고 뷰 컨트롤러는 자신의 뷰를 업데이트하고, 그리고 모델 컨트롤러에게 이를 반영하라고 알릴 거예요. 하지만 이렇게 하면 아까도 봤듯 하나만 업데이트되는 문제가 발생했었죠. 따라서 뷰가 스스로 업데이트하는 코드를 지워버리겠습니다.

이렇게 말이죠! 일단은 모델 컨트롤러에게 새로운 메시지를 추가하기만 합니다.

모델 컨트롤러는 모델에 새로운 메시지를 추가해야겠죠. 그런 뒤엔 연결된 뷰 컨트롤러들에게 뷰를 업데이트하라고 알려줘야 합니다.

이는 어떻게 알려줄 수 있을까요? 개발자는 잘 입력되고 쉽게 디버깅하고 테스트할 수 있도록 이벤트를 패키지화하는 구조화된 방법을 원합니다.

따라서 Swift enum associated 값으로 새로운 타입을 만듭니다. 이는 모델 컨트롤러가 새로운 메시지를 수신할 때 생성할 객체이며 이를 연결된 뷰 컨트롤러 또는 scene으로 보낼 거예요.

이를 알리고 싶으니까 NSNotificationCenter를 사용한 백업 저장소를 사용할 것이고 새로운 업데이트 이벤트를 생성한 뒤 연결된 곳으로  보내는 notification 방법을 추가합니다. 구현은 위에 보이듯이 notification name channel에 notification을 게시하면 됩니다. 여기서 notification 객체에 업데이트 객체 자체를 포함한다는 것이 중요합니다.

이제 모델 컨트롤러가 새 메시지를 입력받았을 때 새로운 이벤트를 만들고 이를 알릴 수 있게 되었습니다.

그런 뒤 뷰 컨트롤러에서는 새로운 이벤트를 관찰하면 됩니다. 옵저버를 추가해서 아까 만든 이벤트를 관찰하면 됩니다. 해당 이벤트가 발생하면 작동하는 handler 메서드를 만들어서 해당 이벤트를 수행하면 됩니다.

 

그럼 이제 어떻게 되는지 볼까요?

아까와 다르게 두 개의 뷰 모두 업데이트가 된 것을 볼 수 있습니다!

 

이렇게 Scene Delegate가 생긴 이유와 도대체 뭐하는 앤지 알아보는 영상을 정리해봤습니다.

감사합니다!

반응형

'Apple > WWDC 2019' 카테고리의 다른 글

[WWDC 2019] Introducing Combine  (1) 2021.10.23
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함