
지난 포스트에서 아예 스토리보드 없이 코드로 UIKit을 작성하기 위해서 어떻게 해야하는지 알아보는 과정에서 자연스럽게 첫 번째로 앱의 생명주기 관리에 대해서 알아봤다. 이번에는 UIScene, UIWindow 등 몇 가지의 클래스와 프로토콜을 간단하게 정리해보려고 한다. 당연하게도 공식문서를 기반으로 작성했기 때문에 이번 포스트에서는 따로 링크를 걸진 않았다.
An object that represents one instance of your app's user instance.
UIKit은 scene object를 유저나 앱이 요청하는 각각의 앱의 UI에 대해 생성한다. 일반적으로, UIKit은 UIScene 객체가 아닌 UIWindowScene 객체를 생성하지만, UIScene의 메서드와 프로퍼티를 이용하여 scene과 관련된 정보들에 접근한다.
각각의 scene 객체는 관련된 delegate 객체가 존재하는데, 해당 객체는 UISceneDelegate를 채택한다. scene의 상태가 바뀔 때, scene 객체는 해당 객체의 delegate 객체에게 알리고 등록되어있는 observer 객체들에게 전달한다. delegate 객체와 알림을 통해서 scene의 상태 변화에 대해서 반응해라. 예를 들어, 이를 이용해서 언제 scene이 백그라운드로 전환될지를 결정해라.
scene 객체를 직접 생성하지는 않는다. 코드상으로 UIKit에게 UIApplication의 메서드인 requestSceneSessionActivation(_:userActivityoptions:errorHandler:) 을 호출하여 앱을 위해 scene 객체를 만들어주길 요청한다. UIKit은 user interaction에 따라 scene을 생성하기도 한다. app의 scene support를 설정할 때, UIScene 객체 대신 UIWindowScene 객체를 지정해라.
An object that contains information about one ofy our app's scenes.
앱의 scene들 중 하나에 대한 정보를 포함하는 객체.
UISceneSession 객체는 scene의 런타임 인스턴스를 관리한다. 유저가 새로운 scene을 앱에 추가하거나 하나를 프로그램 상으로 요청할 때, 시스템은 session 객체를 생성하여 해당 scene을 추적한다. session은 구분되는 identifier와 scene의 설정 디테일들에 대해서 포함한다. UIKit은 세션 정보를 scene의 라이프타임만큼 유지하다가, 유저가 app switcher를 통해서 제거할 때 같이 삭제한다.
session objects를 직접 생성하지는 않는다. UIKit은 session을 앱의 user interaction을 통해 생성한다. UIKit에게 새로운 scene과 session 을 프로그램 상에서 생성해달라고 UIApplication의 requestSceneSessionActivation 메서드를 통해 요청할 수 있다. UIKit은 세션을 앱의 Info.plist 파일을 기반으로 한 default configuration data로 세션을 초기화한다.
The core methods you use to respond to life-cycle events occurring within a scene.
UISceneDelegate 를 통해서 하나의 앱 UI 인스턴스의 life-cycle 이벤트들을 관리해라. 이 인터페이스는 scene에 영향을 주는 상태 전이에 대해서 대응하는 메서드들을 정의한다(예 - scene이 foreground에 들어가서 active 할 때나 background로 들어갈 때). delegate를 이용하여 상태 변화에 적절하게 대응해라. 예를 들어, 앱이 백그라운드로 들어갈 때 중요한 태스크들을 중단시키고 app을 quiet하게 해라.
직접적으로 UISceneDelegate 객체들을 생성하지 마라. 대신, scene에 대한 configuration data의 일부로써 custom delegate class의 이름을 명시해라. 이러한 정보에 대해서 Info.plist에 명시하거나, app delegate의 application(_:configurationForConnecting:options:) 메서드에서 리턴하는 UISceneConfiguration UISceneConfiguration 에 명시해라. scene을 설정하는 것과 관련해서 더 많은 정보를 얻으려면 Specifying the scenes your app supports 참고.
A scene that manages one or more windows for your app
UIScene 부분에서 일반적으로 UIScene 객체가 아닌 UIWindowScene 객체를 생성한다고 했는데 과연 UIWindowScene이 뭔지 알아보자.
UIWindowScene은 하나의 해당 scene에서 포함하고 있는 한 개 이상의 window를 포함하는 app UI 인스턴스에 대해서 관리함. scene object는 유저 디바이스 위의 window에 대해서 관리하고 user와 상호작용하는 scene의 생명주기에 대해서 관리함. scene의 상태가 전이될 때, scene 객체는 UIWindowSceneDelegate를 채택하는 delegate 객체에게 전달한다. 해당 scene은 등록된 observer들에게 적절한 알림을 보낸다. delegate 객체와 notification observer를 이용하여 모든 변화에 대해서 대응해라.
역시나 window scene 또한 직접 생성하는 것이 아니라, app의 Info.plist에서 scene을 위한 class name을 scene configuration detail 안에 configuration time에 UIWindowScene 객체에게 원하는 것을 명시해라. class name을 app delegate 객체의 application(:configurationForConnecting:options:) 메서드 안에서 UISceneConfiguration 객체를 생성할 때 명시할 수도 있다. 유저가 앱과 상호작용할 때, 시스템은 제공된 configuration data를 기반으로 적절한 scene 객체를 생성한다. scene을 프로그램 상으로 생성할 때, UIApplication의 requestSceneSessionActivation(:userActivity:options:errorHandler:) 메서드를 호출해라.
Additional methods that you use to manage app-specific tasks occuring in a scene.
UIWindowSceneDelegate를 이용하여 하나의 앱 UI 인스턴스의 생명주기를 관리해라. window scene delgate는 UISceneDelegate를 채택하며, 이를 통해서 scene이 app 에 연결될 때, 포그라운드로 진입할 때 등등에 대해서 알림을 받을 수 있다. 또한 이를 이용하여, scene 환경의 변화에 대해서 대응할 수 있다. 예로, 만약 유저가 scene의 사이즈를 변경했을 때, delegate를 이용하여 새로운 사이즈에 맞게 변화해야하는 컨텐츠들에 대한 변경을 생성해라.
UIWindowSceneDelegate 또한 직접 객체를 생성하지말아라. 대신, scene에 대한 configuration 데이터의 일부로써 delegate class의 이름을 명시해라. 해당 정보에 대해서 Info.plist나 app delegate 메서드 안에서의 UISceneConfiguration에 명시해라( UISceneDelegate와 동일하니까 프로토타입은 생략, 더 많은 정보 또한 동일한 링크로 들어가서 확인하면 됨).
The backdrop for your app's user interface and the object that dispatches events to your views
window는 view controller와 협업하여 이벤트를 핸들하고 앱의 동작에 필요한 중요한 업무를 수행함. UIKit은 대부분의 window와 관련된 event들을 핸들함.
window를 사용하는 경우는 다음과 같다 :
일반적으로, Xcode는 앱의 메인 윈도우를 제공함. 새로운 iOS 프로젝트는 스토리브드들을 이용하여 앱의 뷰를 정의한다. 스토리보드는 app delegate 객체 내부에 window 프로퍼티의 존재를 필요로 한다(이는 Xcode가 자동으로 생성). 만약 앱에 스토리보드가 없다면 window를 직접 생성해야함.
대부분의 앱은 오로지 한 개의 window만을 필요로 한다. device의 메인 스크린 위에 추가적인 윈도우를 생성할 수 있지만, 추가적인 윈도우는 주로 외부 스크린에 추가적인 컨텐츠를 디스플레이하기 위해서 사용한다. (Presenting content on a connected display 참고)
또한 window 객체를 이용하여 다른 태스크를 수행할 수 있음 :
window는 자체적인 시각적인 모습은 없다. 대신, window는 window의 root view controller에 의해 관리되는 하나 이상의 view를 띄운다.
UIWindow를 상속받을 일은 드물다. window에서 구현하고 싶은 behavior는 상위 view controller에서 보다 쉽게 구현될 수 있다. becomeKey나 resignKey 메서드 오버라이드를 통해서 window key의 상태가 변화할 때에 대해서 건드릴 때, 상속을 필요로 한다. 특정 스크린에서 어떻게 window를 띄울지에 대해서는 UIScreen을 참고해라.
지난 포스트와 이번 포스트에 거쳐서 scene을 기반으로 한(iOS13 ~) 앱의 라이프 사이클과 관련된 개념들과 클래스, 프로토콜에 대해서 공식문서를 기반으로 요약했다. 직접적으로 다룰 일이 지금까지 없었기 때문에 정리하면서도 꽤나 애를 먹었고 헷갈리는 것 또한 많았다.
특히 앱의 상태에서 suspended와 unattached가 헷갈렸는데, suspended 상태는 foreground로 돌리던 앱을 app switcher를 통해서 background 상태로 보냈을 때, 일정 시간이 지나면 메모리 상에는 남아있지만 CPU 점유율은 0인 상태로 유지되고, 해당 앱을 app switcher에서 우리가 밀어서 제거한 경우에 대해서 unattached 상태, 즉 not running 상태로 간다고 이해했다. 이렇게 짧게 정리한 내용에 대해서도 간혹 블로그에서 명확하게 잘못 정리한 것도 확인한 것을 봤을 때, 확실히 나만 헷갈리는 것은 아닌 것 같다. 이 부분에 대해서 따로 상태별로 정리한 문서도 있었으면 좋았을 것 같다.
이번 포스트 내용 중에서는 아무래도 UIScreen, UIScene, UIWindow, UIView 사이의 관계를 이해했다는 것에서 만족한다. 뭐 UI의 생명주기를 관리하고 직접 생성하지 않아야한다는 공통적인 내용이 있었지만, Info.plist랑 app delegate의 어떤 메서드 안에서 어디에 알려주거나 이런 것들은 한번도 써본적이 없어서 기억에 거의 없지만, 이 또한 나중에 결국 알게될 것이라고 믿는다.
그러면 내가 헷갈렸던 UIScreen, UIScene, UIWindow에 대해서도 정리해볼까 한다.

처음부터 뷰 계층 디버거를 통해서 확인했으면 조금 덜 헷갈렸을 수도 있을 것 같다. 뷰야 해당 포스트를 읽는 분들이라면 다 알 것이라고 생각하고, UIScreen은 물리적인 디바이스의 스크린을 의미한다. 그렇다면 UIWindow는 뭐고 UIScene이 뭔지가 문제다. 나도 이 둘이 엄청 헷갈렸고, 하필 이름도 UIWindowScene이다. 도대체 이 친구는 UIWindow를 상속받는 것인지 UIScene을 상속받는 것인지 헷갈렸는데, 뭐 결국 Jump to definition으로 확인하거나 공식문서를 확인하면 바로 알 수 있다싶이 UIScene이다. UIScene을 직접적으로 사용하는 경우보다 UIWindowScene을 사용하는 것이 보편적이라고 했지만 , 그냥 내 생각에 UIWindowScene이 UIScene이라고 보는 것이 마음 편할 것 같다. 위의 캡쳐 사진에서 제일 앞에 있는 layer가 당연하게도 view고, 마지막 layer는 UIWindow다. 한마디로 window 위에 뷰들을 붙이는 느낌이다.

UIWindow는 일반적으로 하나만 있으면 충분하고 이 위에 뷰를 붙인다. 그리고 이 window가 자신이 포함하고 있는 뷰와 관련된 이벤트들을 관리한다. 비유하자면, window라는 게시판에 view라는 여러 개의 공고와 광고 등을 붙이는 것과 같다고 느껴졌다.
블로그였는지 공식문서였는지 얼핏 봤을 때는 아이패드의 split view 등에서는 복수의 window를 가질 수 있다고 들었다. 아이패드는 없어서 직접 본 적은 없지만 무슨 느낌인지는 알 것 같다. 이렇게 생성한 하나 이상의 윈도우를 scene에서 관리한다.
추가적으로 UISceneSession을 통해서 런타임에 있는 UIScene 인스턴스를 관리하고, scene당 session을 하나씩 만들어서 해당하는 scene을 tracking 하는 것 정도까지 정리하면 얼추 두 포스트에 걸친 내용을 모두 요약한 것 같다.
확실히 익숙하지 않은 개념들을 다루다보니, 이렇게 공식문서를 정리해도 명확하게 와닿지는 않는다. 그래도 회고를 제외한 첫 포스트인데, 나름 내가 어떻게 평소에 학습하는지를 알 수 있게 작성한 것 같긴하다. 비록 원하는 것 (이번 포스트에서는 UIScene, UIWindow, UIScreen이 뭔지)을 위주로 파악하고 나머지는 전부 꼼꼼하게 읽기보다는 어떤 클래스를 상속받고, 어떤 역할을 하는지 키워드를 빠르게 훑어본 뒤에 어떤 프로퍼티와 메서드를 공유하는지 정도만 정리하는 편이긴 하다.
아마도 추후에는 이번 포스트와는 다르게 조금 더 기존에 하던 방식대로 접근할 것 같긴하다. 아무리 완벽하게 이해를 하지 못했더라도, 앱의 lifecycle과 관련된 내용이다보니 조금 더 꼼꼼하게 정리하는게 맞지 않을까 싶은 마음에서 전문을 모두 해석하게 되었다.
나중에 AppDelegate가 궁금해질 때 3편으로 UIApplication과 UIApplicationDelegate를 다뤄야겠다.