안녕하세요. windowHyeok입니다.
이번 글에서는 어플리케이션의 수명 주기(Application LifeCycle)에 대해서 정리해보려고 합니다.
사람의 수명주기처럼 어플리케이션에도 수명주기가 존재합니다.
사람의 수명주기에서 연령에 따른 특수한 이벤트, 해야할일이 있는것 처럼
(8살에는 초등학교 입학, 고3에는 수능을 치뤄야 하는 등등)
앱을 제작하면서도 분명 각 앱의 상태에 따른 처리가 필요한 부분이 있습니다.
이럴 때 iOS 환경에서는 각 상황에 대한 케이스를 정의하고,
케이스에 해당할때마다 호출되는 메소드를 활용하여 앱의 생명주기에 따른 동작과 구성을 처리할수 있습니다.
여기서 또 os 버전에 따른 차이가 생깁니다.
iOS 12및 이전버전에서는 UIAplicationDelegate 객체에서 앱의 수명주기를 처리했으나,
iOS 13 이후 버전에서는 UISceneDelegate 객체에서 앱의 수명주기와 Scene 기반의 화면 처리를 담당하고 있습니다.

iOS 앱의 생명주기를 잘 나타내는 이미지입니다. 차례대로 살펴볼까요?
1. 사용자가 앱 아이콘을 터치하여 어플리케이션을 실행합니다.
2. 이후 main() 메서드가 실행되고, 함수 내부에서 UIApplicationMain 메서드 호출됩니다.
: 이때 프로젝트에서 AppDelegate 클래스에 작성된 application:didFinishLaunchingWithOptions: 가 실행됩니다.
이 함수에 원하는 code를 작성해두면 앱이 처음 시작할때 해당 코드를 실행한다고 하네요.
3. 이후 시스템 프레임워크의 event Loop가 실행되면서 사용자가 작성하는 Handle Event에 의해 custom 코드로 연결됩니다.
-> 여기서의 Handle Event는 기기를 통해 사용자가 입력하는 이벤트를 통 틀어서 생각하면 될것 같습니다.(버튼 터치, 텍스트 필드 입력 등등..)
4. 앱이 실행되다가 앱의 실행 목적을 모두 완료하여 종료되는 경우 시스템은 메모리에서 앱을 제거합니다.
-> 이 과정에서 AppDelegate 클래스의 applicationWillTerminate(_:) 메소드를 호출합니다. 앱 종료 전에 처리해야 될 작업이 있다면, 이 메소드 내부에 custom code를 작성하면 됩니다.
앞서 지원하는 버전에 따라 앱의 생명주기를 처리하는 객체가 다르다고 말씀드렸습니다.
12 이하의 버전에서는 AppDelegate, 13 이상의 버전에서는 SceneDelegate가 앱 수명 주기를 관리합니다.


여기서 궁금한 점이 생겼습니다. 기존의 Window에서 새로운 Scene의 개념을 추가하고 SceneDelegate를 따로 만들어서 처리하게 된 이유는?
iOS 13 이후부터는 Scene을 사용하여 하나의 앱에서 2개 이상의 여러개의 화면을 사용할수 있게 되었기 때문입니다.
추가적으로 제 뇌피셜이긴 하지만,기존에는 하나의 앱에서 Window를 사용하여 하나의 UI에 대한 생명주기만 처리하면 되었기 때문에 AppDelegate에서 처리가 가능했으나,
iOS 13이후 Scene을 사용하여 하나의 앱에서 여러개의 UI 생명주기를 처리해야 되는 상황이 되면서 AppDelegate에서 SceneDelegate에게 역할을 위임하여 각 객체에서 명확한 책임분리를 할수 있도록 한게 아닌가 싶네요.
AppDelegate에서 모든 역할을 수행하고 처리했다면, 코드 작성시 AppDelegate가 비대해지고 무거워졌을거 같은데,
SeceneDelegate에 UI 생명주기를 처리하게 해서 코드를 편하게 작성하고 알아보기 쉽게 하기 위함이 아닌가 싶습니다.
점선으로 표시된 경로는 시스템 플로우, 실선으로 표시된 경로는 유저 플로우 입니다.

앱이 실행되었다가 종료되거나, 앱이 실행되지 않은 초기 상태입니다.
앱이 전면(Foreground)에서 실행되고 있지만, 사용자의 동작을 받을 수 없는 상태입니다.
ex) 알림창을 내렸을때, 앱 스위칭 할때, 시스템 알림을 받을때
앱이 전면(Foreground)에서 실행중이며, 이벤트를 받을 수 있는 상태입니다.
앱이 화면에 표시되지 않지만 여전히 실행되고 있는 상태입니다.
ex)유튜브 프리미엄의 백그라운드 재생 기능
Background에서 정지된 상태입니다.
만약 메모리가 부족한 상황이라면, iOS 시스템은 앱의 전면(Forground)에 있는 앱의 여유 메모리 공간을 확보하기 위해 Suspended 상태에 있는 앱들을 특별한 알림 없이 종료합니다.
(Suspended -> Not Running 상태로 진입 하는 것은 알림을 따로 받지 않는다.)
//didFinishLaunchingWithOptions
- 애플리케이션이 실행된 직후 사용자의 화면에 보여지기 직전에 호출
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
//willFinishLaunchingWithOptions
//애플리케이션이 최초 실행될 때 호출되는 메소드
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool
//applicationWillResignActive
//애플리케이션이 InActive 상태로 전환되기 직전에 호출
func applicationWillResignActive(_ application: UIApplication)
//applicationDidEnterBackground
//애플리케이션이 백그라운드 상태로 전환된 직후 호출
func applicationDidEnterBackground(_ application: UIApplication)
applicationWillEnterForeground
//애플리케이션이 Active 상태가 되기 직전, 화면에 보여지기 직전에 호출
func applicationWillEnterForeground(_ application: UIApplication)
//applicationDidBecomeActive
//애플리케이션이 Active 상태로 전환된 직후 호출
func applicationDidBecomeActive(_ application: UIApplication)
//applicationWillTerminate
//애플리케이션이 종료되기 직전에 호출
func applicationWillTerminate(_ application: UIApplication)
점선으로 표시된 경로는 시스템 플로우, 실선으로 표시된 경로는 유저 플로우 입니다.

유저나 시스템이 앱의 새로운 scene을 요청하면 UIKit은 이를 생성하고 연결되지 않은 상태로 둔다.
사용자가 요청하는 Scene은 빠르게 foreground로 이동한다.
시스템이 요청한 Scene은 이벤트를 처리할 수 있도록 background로 이동한다.
유저가 앱을 닫으면 UIKit은 관련 scene을 백그라운드 상태로 이동하고 결국 suspended 된다.
단계가 사라짐
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
...
}
//우선 SceneDelegate는 UIWindowSceneDelegate 프로토콜을 채택하고 있고 여기서 이름으로 유추할 수 있듯이 window 화면 그리는데 관련된 작업을 수행하는 것을 알 수 있다.
//willConnectTo
//iOS 12의 application (_ : didFinishLaunchingWithOptions :) 같은 기능
// scene이 앱에 추가될 때 호출
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
//sceneDidDisconnect
//scene의 연결이 해제될 때 호출
func sceneDidDisconnect(_ scene: UIScene)
//sceneDidBecomeActive
//app switcher에서 선택되면 inActive 상태에서 active상태로 전환
func sceneDidBecomeActive(_ scene: UIScene)
//sceneWillResignActive
//active -> inactive
func sceneWillResignActive(_ scene: UIScene)
//sceneWillEnterForeground
//background -> foreground
func sceneWillEnterForeground(_ scene: UIScene)
//sceneDidEnterBackground
//foreground에서 backgRound로 전환시
func sceneDidEnterBackground(_ scene: UIScene)
//앱 실행시 AppDelegate, SceneDelegate에서 메서드 실행 흐름
// AppDelegate -> SceneDelegate -> AppDelegate
//1. 애플리케이션이 실행된 직후 사용자의 화면에 보여지기 직전에 호출
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
applications.configurationForConnecting ->
//2. scene이 앱에 추가될 때 호출
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
//3. scene의 연결이 해제될 때 호출
func sceneDidDisconnect(_ scene: UIScene)
//4. 애플리케이션이 종료되기 직전에 호출
func applicationWillTerminate(_ application: UIApplication)
각 메서드의 호출 순서를 확인했는데,
기본적으로 앱의 맨 처음과 마지막은 AppDelegate의 메서드가 실행되고,
중간 안쪽 부분에서는 SceneDelegate 메서드가 실행되었습니다.
이러한 흐름으로 생각했을때 앱 전체적인 부분에서의 역할(실행과 종료)는 AppDelegate에서 처리하고,
UI 생명주기 관련 화면에 관한 처리는 SceneDelegate에서 처리한다는 흐름이 순차적으로 이해되어서 많은 공부가 되었습니다.
래퍼런스 참고
https://limjs-dev.tistory.com/58
https://jouureee.tistory.com/65
https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle