[WWDC] Architecting Your App for Multiple Windows

J.Noma·2021년 11월 27일
0

iOS : View : UIKit

목록 보기
12/17

iOS 13부터 multiple window 기능과 함께 추가된 Scene Delegate에 대한 WWDC 세션을 요약한 포스팅입니다


🐶 기존의 App Delegate

✔ 기존 App Delegate의 역할

기존의 App Delegate는 2가지 주요한 역할을 가졌습니다

1. Process-level 이벤트 수신하기
시스템은 App Delegate에게 process가 launch되고 종료될 예정임을 알립니다

2. UI의 상태 이벤트 수신하기
foreground로 간다던지 inactive된다던지 등의 상황에서 시스템은 App Delegate에게 UI상태를 알립니다

이런 구조는 iOS 12까지는 완전히 괜찮았습니다
왜냐하면 App이 하나의 process만 가지고 그에 대응되는 하나의 UI 인스턴스만 가졌기 때문입니다

✔ 예제

AppDelegate는 아래와 같은 실루엣을 보입니다

예제에서 application()에서 크게 2가지 일을 합니다

1. non-UI global setup
Database.connect()
DB에 연결하거나 data 구조를 초기화하는 등

2. UI setup
window = UIWindow()


🐺 새로운 App Delegate, 그리고 Scene Delegate

✔ App Delegate의 역할 변화가 필요하다

다시 말하면, 이런 구조는 iOS 12까지는 완전히 괜찮았지만 iOS 13부터는 invalid합니다


왜냐하면 위 사진처럼 현재 App이 여전히 하나의 프로세스(메모 App)를 공유하지만 여러 개의 UI 인스턴스 혹은 여러 개의 Scene Session을 가질 수 있기 때문입니다

이는 App Delegate의 역할 변화가 필요함을 의미합니다

✔ Scene Delegate가 위임받을 역할

여전히 Process Life-cycle에 대한 책임은 있지만
UI Life cycle과 관련한 것은 어떤 것도 책임이 없습니다

대신 이것들은 SceneDelegate에 의해 처리됩니다

이게 우리에게 무슨 의미일까요?

App Delegate에서 UI를 setup하고 해체하는 모든 작업
지금은 Scene Delegate의 메서드로 이주시키는 것이 필요합니다

실제로, iOS 13에서 우리의 App은 'Scene Life cycle'이란걸 채택하고
UIKit은 UI 상태와 관련한 옛날의 App Delegate메서드를 호출하지 않습니다
대신, 새로운 Scene Delegate 메서드를 호출하며 이는 꽤 단순합니다 (대부분 1대1로 대응되기 때문)

✔ iOS 12 이하 버전과의 호환성 문제

만약 iOS 13에서 multiple window support를 채택하고 싶다고해서 iOS 12 이하 버전을 drop할 필요는 없습니다

만약 이전 버젼에 대한 배포를 하려면, 단순히 이 메서드를 양쪽 모두 구현하면 됩니다
UIKit이 런타임에 적절한 것을 호출할 것입니다


🐱 Scene Session


구체적인 delegate 메서드에 대해 알아보기 전에, 이로 인해 추가된 App Delegate의 책임에 대해 짚고 갑시다
시스템이 App Delegate에게 언제 새로운 Scene Session이 생성/삭제되는지 알립니다

이 life cycle을 좀 더 자세하게 알아봅시다

어떤 App을 처음으로 launch하는 경우의 call stack을 겪어봅시다

1. App Delegate의 didFinishLaunching가 호출됨

App의 one-time non-UI setup이 여기서 수행된다


2. App Delegate의 configurationForSession를 호출하여 Scene Session 생성

실제로 UI Scene을 만들기 전에 App에게 UISceneConfigruation을 요청
이 configuration은 무슨 scene delegate / 무슨 스토리보드 / (명시했다면) 무슨 Scene subclass를 지금 만드려는 scene과 함께 만들지 명시합니다

이 configuration은 info.plist를 통해 정적으로 넣어줄 수도 있고, 코드로 동적으로 넣어줄 수도 있습니다
아마 당신은 main configuration을 가질 수 있고 accessory scene?을 가질 수도 있습니다
여기서 제공되는 option parameter를 봐야합니다.
이것들을 적절한 scene configuration을 선택하기 위한 context로 사용됩니다

일단 configuration을 정의하면 (예로, info.plist에서) 당신은 위 그림과 같이 간단하게 name을 기반으로 이것을 참조하면 되고 parameter로 전달된 sessionRole을 전달합니다

이제 App이 launch되었고 scene session을 확보하였지만 아직 어떠한 UI도 볼 수 없습니다


3. Window를 생성하고, Scene Delegate의 willConnectToSession을 호출하여 Session에 연결

UI window를 setup하여 지금까지 만든 Session에 연결하는 곳입니다

(우리는 또한 window를 함께 설정하는 관련 유저 activity나 상태 보존 activity를 확인할 필요가 있는데 이건 잠시 후에 좀 더 알아보겠습니다)


🐸 홈 화면으로 가는 상황

만약 유저가 swipe up해서 홈화면으로 돌아가면 어떻게 될까요?

1. 이미 친숙하듯이, active를 resign하고 didEnterBackground 메서드가 Scene Delegate에서 호출됩니다

이후 어느 시점에 우리의 Scene은 disconnect될 것입니다
이게 무슨 말일까?


2. 자원을 회수하기 위해, 시스템은 scene이 background로 진입한 후 어느 시점에 scene을 메모리로부터 해제시킵니다

이것은 또한 당신의 scene delegate가 메모리로부터 해제되고 scene delegate이 잡고 있던 window hierarchy, view hierarchy도 해제됨을 의미합니다

이것은 이 scene과 관련된 App이 갖고 있던 큰 메모리 자원을 해제할 기회를 주는 것입니다

하지만 이게 실제로 유저 data나 상태를 영구적으로 삭제하는데 사용하지 않는게 중요합니다
그 scene이 나중에 재연결되고 돌아올 것이기 때문입니다


🦀 Switcher에서 swipe up하는 상황

유저가 Scene을 destroy하기 위해 switcher(위 사진)에서 위로 올려서 종료시켜버리면 어떻게 될까?

1. App Delegate의 didDiscardSceneSessions이 호출됩니다

이는 결국 scene에 있던 유저 data와 상태를 실제로 영구적으로 삭제할 기회를 주는 것입니다
(ex. text 편집 app에서 저장되지 않은 draft같은 것들)

이 시점에서 App process가 진행 중이지 않던 상태라면(=suspend)
유저가 swipe up을 하면서 하나 이상의 UIScene을 제거될 수 있습니다

이처럼 process가 진행 중이지 않아 시스템에 의해 제거된 session
시스템이 계속해서 추적하고 App이 다음에 launch된 직후에 이를 호출합니다
(ex. 시계 App에서 '알람 편집' 창에서 swipe up하고, App을 재실행하면 자동으로 '알람 편집'창으로 이동됨)


🐔 State Restoration

이제 App에 구현하는 것을 고려해볼만한 몇 가지 아키텍쳐 패턴에 대해 알아봅시다

먼저, '상태 복구'입니다

iOS 13에서의 상태복구는 더이상 꼼꼼하지 않습니다
다만, 당신의 App이 scene-based 상태 복구를 구현한다는 것이 중요합니다

✔ 복구가 필요한 예시


App switcher에서 하나의 Document App에 대해 4개의 서로 다른 session을 열어놓은 상태입니다

하지만 여기서 'packaging list'와 'agenda'라는 두 session만 foreground에 두어 봅시다

어느 시점에, 나머지 두 session은 시스템에 의해 disconnect & release될 것입니다

만약 여기서 상태 복구를 구현하지 않는다면,
해제된 session에 돌아갔을 때 이전에 있던 상태를 불러오지 못할 것입니다

즉, 내가 편집 중이던 document를 띄우지 않고 새로운 window처럼 처음부터 다시 시작할 것입니다
이는 좋은 유저 경험이 아닙니다

✔ Scene-based State Restoration


어떻게 이걸 해결할까요? iOS 13은 새로운 scene-based 상태 복구 API를 가집니다

이것은 view hierarchy를 인코딩하지 않고, 대신 재생산해야 할 window의 상태를 인코딩합니다

또한 이는 모두 NSUserActivity 에 기반합니다

그래서 만약 당신의 App이 spotlight search나 handoff처럼 강력한 기술을 활용한다면,
App의 상태를 인코딩하기 위해 이 기술들을 사용할 수 있습니다?
(spotlight search와 handoff도 NSUserActivity와 연관있음)

또한, 당신이 시스템에게 돌려주는 상태 복구 저장소의 data protection 등급은 App의 나머지 부분의 등급과 동일합니다?

✔ 상태 복구 구현하기

1. Scene delegate에서 우리는 scene을 위한 상태 복구 activity를 구현하고
-> stateRestorationActivity()

2. 그 안에 현재 window에서 most active한 관련 유저 activity를 찾아 return합니다
-> fetchCurrentUserAcitivty()

잠시 후, scene이 다시 foreground로 진입하거나 connect되면

3-1. 우리는 session이 상태복구 activity를 갖고 있는지 확인하여 사용합니다
-> scene() 메서드의 if문

3-2.백업된게 없다면 그냥 새로운 window를 생성합니다

이는 아무튼, 유저들이 언제 scene이 background에서 disconnect되더라도 알아채지 못함을 의미합니다


🐻 Scene간 동기화 문제

마지막으로 multiple window를 적용하면서 만나게 될지도 모를 중요한 이슈를 하나 더 알아봅시다

그것은 'App의 scene들을 어떻게 동시에 최선으로 유지할까'입니다

✔ 동기화 문제 예시


채팅 App을 예로 multiple window를 사용하는 예시를 알아봅시다
친구 A와 채팅 중인데 이 하나의 conversation을 두 개의 뷰컨/scene으로 동시에 보고 있는 상황입니다

왼쪽 scene에서 A에게 메세지를 보냈지만 같은 conversation인 오른쪽 scene에는 업데이트되지 않았습니다

✔ 문제가 되는 App 구조


그 이유는 많은 App들이 버튼탭 -> 뷰업데이트 -> 모델업데이트 순으로 동작하기 때문입니다

이 방식은 하나의 UI 인스턴스를 다룰 땐 거의 문제가 없지만
지금처럼 하나의 data를 여러 개의 scene이 다루는 경우
다른 scene에게는 new data에 대한 업데이트를 알리지 않습니다

우리는 방식을 바꾸어 이를 해결할 수 있습니다

✔ 개선된 App 구조


아키텍쳐적으로 뷰컨에 이벤트가 들어오면 이를 모델에게 바로 알립니다.
그리고 모델이 실제로 뷰컨들에게 new data 업데이트를 알리는 방식입니다

이 외에도 delegate나 notification같은 여러 방식들이 있습니다
심지어 19년도에 나온 Combine framework를 사용할 수도 있습니다


코드로 보면

1. 버튼이 눌리면 Model을 업데이트하도록

2. Model이 바뀌면 NotificationCenter로 뷰컨들에게 뿌린다
(예제에선 디버그/테스트 용이성을 위해 별도의 타입을 만듦)

profile
노션으로 이사갑니다 https://tungsten-run-778.notion.site/Study-Archive-98e51c3793684d428070695d5722d1fe

0개의 댓글