개요
iOS에서의 앱 생명주기는, iOS 13 전후로 작동 방식이 다르다.
iOS 13부터는 앱 하나를 여러 개의 창으로 띄우는 멀티 윈도우 기능을 지원하게 되면서 생명주기 관리 방식이 기존보다 더 세분화되었으며, 이에 따라 앱의 생명주기 관리 구조 역시 변경되었다.
앱 생명주기에 대해 설명하고 멀티 윈도우 도입 전후의 차이점을 비교하여, 실제로 이를 어떻게 핸들링할 수 있는지 설명하고자 한다.
목차
- 앱의 상태 및 생명주기
- 멀티 윈도우 도입에 따른 변화
- iOS 13 이전과 이후 변화된 생명주기 관리 방식
- iOS 13 이전과 이후, 생명주기를 고려한 iOS 호환성을 유지하는 방법
앱의 상태 및 생명주기
앱의 생명주기란, 앱의 상태가 전환되는 일련의 과정을 말한다. 앱 상태의 경우 크게는 세 가지, 더 세분화된다면 다섯 가지로 나눌 수 있다.

[앱 상태]
Not Running
앱이 완전히 종료된 상태. 앱이 전혀 동작하지 않는 상태이다.
Foreground
앱이 실행되었으며, 화면에 보이는 상태이다.
- Inactive(in Foreground)
- Active(in Foreground)
Background
앱이 실행되었으나, 화면 상에 보이지 않는 상태이다.
- Running(in Background)
- Suspend(in Background)
앱의 생명주기는 위에서 설명한, 상태 간 전환을 일컫는다.
이 생명주기 관리 방식은 앞서 말했듯이 iOS 13 이전과 이후의 멀티 윈도우 기능의 등장으로 변화되었다고 하는데, 그렇다면 멀티 윈도우 및 관련된 변화는 무엇일까?
멀티 윈도우 도입에 따른 변화
멀티 윈도우 도입 전(iOS 13 이전)
기존의 iOS의 경우, 하나의 스크린에 최대 하나의 창을 표시할 수 있었다.

멀티 윈도우 기능이 도입되기 전, 기존의 UI 구조이다.
실제 디바이스 스크린에 대응되는 UIScreen 위에 곧바로 UI 컨테이너인 window(UIWindow) 객체가 올라간다. 그림과 같이 여러 개의 window 객체를 생성할 수는 있지만, 하나의 스크린에 최대 하나의 window(key window)만이 보여질 수 있다.
멀티 윈도우 도입 후(iOS 13 이후)

iOS 13 이후의 새로운 OS에서, 일부 디바이스에서 하나의 스크린에 위와 같이 여러 창을 띄우는 멀티 윈도우 기능이 등장하였고, 이에 맞춰 기존의 UI 구조 역시 변경되었다.

멀티 윈도우 기능이 등장한 후, 새롭게 변경된 UI 구조이다.
실제 디바이스 스크린에 대응되는 UIScreen 위에 새롭게 scene(UIWindowScene) 객체가 올라가며, 해당 scene 위에 기존 UI 컨테이너 역할을 하는 UIWindow가 올라간다.
window와는 다르게 UIScreen 위에 여러 개의 scene이 동시에 올라갈 수 있으며, 이러한 UI 구조로 멀티 윈도우 기능이 동작하게 된다.
iOS 13 이전과 이후 변화된 생명주기 관리 방식
그렇다면 어떻게 생명주기에 맞추어 어플리케이션 로직을 관리할 수 있을까?
기본적으로는 생명주기에 맞춰, 시스템이 앱 내에 정의된 특정 함수를 호출하게 된다. 해당 함수에 대해 개발자가 정의함으로써 앱의 생명주기에 맞춰서 리소스를 관리하는 등, 특정 로직을 수행하도록 만들 수 있다.
이를 수행하는 파일 및 함수의 경우 앞서 설명한 iOS 13 이전과 이후로 변경되는데, 기존의 생명주기 관리 방식을 먼저 소개하고 scene 등장 이후 변경된 생명주기 관리 방식에 대해 소개해보겠다.
기존 생명주기 핸들링(iOS 13 이전) - AppDelegate Only

기존 생명주기 관리의 경우 하나의 스크린에 하나의 window만 존재하며, 해당 window의 상태에 따라 어플리케이션 전체의 생명주기가 변경된다.
따라서 window 및 앱 초기화를 다루는 파일인 AppDelegate 파일 내에서 앱의 모든 생명 주기를 다룬다.
AppDelegate - 생명 주기 관련 메소드 리스트
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
print("willFinishLaunchingWithOptions : 어플리케이션이 메모리에 로드된 직후 호출")
print("기본 설정이나 초기 리소스 로딩 등을 수행\n")
return true
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
print("didFinishLaunchingWithOptions : 어플리케이션이 메모리에 로드된 후, 화면에 로드되기 직전에 호출")
print("UI 설정, 뷰 컨트롤러 초기화 등을 진행\n")
return true
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
print("handleOpen : URL Scheme이 지정된 경우, 해당 URL을 처리할 때 호출")
print("다른 앱이나 브라우저에서 전달된 URL을 처리할 로직을 구현\n")
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
print("applicationDidBecomeActive : 어플리케이션이 액티브 상태가 되었을 때 호출")
print("사용자 인터랙션을 재개, 일시 정지된 작업을 재개\n")
}
func applicationDidEnterBackground(_ application: UIApplication) {
print("application did become background : 어플리케이션이 완전히 백그라운드 상태에 들어갔을 때 호출")
print("데이터를 저장하거나 백그라운드 작업을 시작\n")
}
func applicationWillTerminate(_ application: UIApplication) {
print("application will terminate : 어플리케이션이 종료되기 직전에 호출")
print("종료 전에 필요한 데이터를 저장하고 리소스를 정리하는 작업\n")
}
func applicationWillResignActive(_ application: UIApplication) {
print("application will resign active : 어플리케이션이 비활성화 상태로 전환될 때 호출")
print("현재 진행 중인 작업을 일시 중지하거나 상태를 저장\n")
}
func applicationWillEnterForeground(_ application: UIApplication) {
print("application will enter foreground : 어플리케이션이 foreground 상태로 전환되려 할 때 호출")
print("포그라운드 진입 전 UI 업데이트나 데이터 동기화를 준비\n")
}
앱의 상태 변경에 따른 모든 생명주기를 오직 AppDelegate 파일 하나 내에서 관리함을 알 수 있다. 그렇다면 이후 변경된 생명주기 관리는 어떠한 방식으로 이루어질까?
변경된 생명주기 핸들링(iOS 13 이후) - AppDelegate & SceneDelegate

이에 따라, 기존 window보다 세분화된 scene 환경에서의 생명주기를 다루기 위해, 기존 AppDelegate 외에도 SceneDelegate 파일이 등장하게 되었다.
변경된 생명주기 관리의 경우 하나의 스크린에 여러 개의 scene이 존재할 수 있기 때문에 스크린 위에 표기되는 최상단 UI 단위가 window에서 scene으로 변경되었으며, 또한 어플리케이션의 상태가 한 scene의 상태만으로 결정되지 않게 되었다(scene이 두개가 있다가 하나가 사라진다고 해서, 어플리케이션이 종료되지 않으므로)

따라서 AppDelegate에서 관리하던 모든 생명주기 메소드 중 일부가 scene을 관리하는 SceneDelegate 파일로 옮겨가게 되었고, 해당 SceneDelegate 파일에서 각 scene의 생명주기를 관리하게 되었다.
변경된 AppDelegate 생명주기 메소드 그리고 SceneDelegate의 생명주기 관련 메소드 리스트
Scene 단위의 생애주기 관리를 위해 몇 가지의 관련 메소드가 SceneDelegate가 넘어갔으나, AppDelegate의 경우 여전히 어플리케이션의 시작 및 종료에 대한 관리를 해 주어야 한다.
이를 위해 각 Scene에 대한 정보를 넘겨받는 함수가 추가되었고, 이 함수 내에서 SceneSession 객체를 통해 scene의 상태를 추적, 어플리케이션 전체 상태를 관리할 수 있다.
아래는 Scene의 상태를 전달받는 메소드가 추가된 AppDelegate, 그리고 scene의 생애주기를 관리하는 SceneDelegate의 메소드 리스트이다.
AppDelegate(전체 프로세스 생명주기 관련 메소드만을 수행)
// AppDelegate 생명주기 메소드 리스트
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
print("willFinishLaunchingWithOptions : 어플리케이션이 메모리에 로드된 직후 호출")
print("기본 설정이나 초기 리소스 로딩 등을 수행\n")
return true
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
print("didFinishLaunchingWithOptions : 어플리케이션이 메모리에 로드된 후, 화면에 로드되기 직전에 호출")
print("UI 설정, 뷰 컨트롤러 초기화 등을 진행\n")
return true
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
print("handleOpen : URL Scheme이 지정된 경우, 해당 URL을 처리할 때 호출")
print("다른 앱이나 브라우저에서 전달된 URL을 처리할 로직을 구현\n")
return true
}
func applicationWillTerminate(_ application: UIApplication) {
print("application will terminate : 어플리케이션이 종료되기 직전에 호출")
print("종료 전에 필요한 데이터를 저장하고 리소스를 정리하는 작업 등을 수행\n")
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
print("configurationForConnecting : 새로운 scene이 생성될 때 호출")
print("해당 scene의 상태를 보고할 sceneSession의 구성을 위한 configuration 메소드\n")
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
print("didDiscardSceneSessions : scene session이 삭제될 때 호출\n")
}
SceneDelegate(각 Scene의 생명주기에 맞추어 메소드 실행)
// SceneDelegate 생명주기 메소드 리스트
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
print("scene - willConnectTo : scene이 앱에 연결될 때 호출")
print("scene session의 구성과 초기 UI 설정 등을 수행\n")
guard let _ = scene as? UIWindowScene else { return }
}
func sceneDidDisconnect(_ scene: UIScene) {
print("scene - sceneDidDisconnect : scene이 해제될 때 호출")
print("scene과 관련된 리소스 해제 작업 수행\n")
}
func sceneDidBecomeActive(_ scene: UIScene) {
print("scene - sceneDidBecomeActive : scene이 활성화되었을 때 호출")
print("중단되었던 작업을 재시작\n")
}
func sceneWillResignActive(_ scene: UIScene) {
print("scene - sceneWillResignActive : scene이 비활성화되기 직전에 호출")
print("일시 정지할 작업 처리 (예: 타이머 정지)\n")
}
func sceneWillEnterForeground(_ scene: UIScene) {
print("scene - sceneWillEnterForeground : scene이 포그라운드로 들어오기 직전에 호출")
print("백그라운드 진입 시 변경했던 내용을 복원\n")
}
func sceneDidEnterBackground(_ scene: UIScene) {
print("scene - sceneDidEnterBackground : scene이 백그라운드로 전환될 때 호출")
print("데이터 저장, 공유 리소스 해제 등 백그라운드 전환 관련 작업 수행\n")
}
이와 같이 scene의 등장 이후로는 AppDelegate의 일부 로직이 SceneDelegate로 넘어가게 되어, 이제는 AppDelegate는 공통 생명주기 로직을 관리, SceneDelegate는 각 scene에 해당하는 생명주기 로직을 관리함을 알 수 있다.
생명주기를 고려한 iOS 호환성 유지
iOS 버전에 따라 디바이스 내 scene 유무가 달라진다면, 단순히 하나의 코드만을 작성하는 것으로는 iOS 13 이전과 이후의 디바이스 환경 모두를 정상적으로 작동시킬 수 없을 것이다. 어떤 식으로 iOS 버전에 따른 호환성을 유지할 수 있을까?
iOS 13 이후의 프로젝트의 경우 자동으로 AppDelegate 및 SceneDelegate 관련 설정이 완료되므로, 배포 대상이 iOS 13 이상만일 경우는 별도의 처리가 필요하지 않다.
iOS 13 미만과 이상 모두가 배포 대상일 때의 처리 방법과, 최신 프로젝트에서 배포 대상이 scene이 존재하지 않는 iOS 13 미만일 경우의 구조 변경에 대해서 다루어보고자 한다.
iOS 13 미만과 13 이상이 모두 배포 대상일 때
아래 키워드를 활용하여, iOS 버전에 따라 분기 처리하면 된다.
#available(iOS 13, *)
iOS 13 이후의 경우에만 true를 반환하는 메소드이다.(macOS 등 다른 디바이스의 경우 기본적으로 true를 반환)
AppDelegate
import UIKit
@UIApplicationMain
class AppDelegate : UIResponder, UIApplicationDelegate {
var window : UIWindow?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey : Any]?)
-> Bool {
if #available(iOS 13, *) {
// iOS 13 이상일 경우, UI 관련 작업을 제외한 앱 초기 설정 등만을 수행
// UI 관련 작업은 SceneDelegate 내에서 수행한다.
} else {
// iOS 13 미만일 경우 SceneDelegate가 없으므로
// AppDelegate에서 직접 window 생성 및 초기화 기능을 수행한다.
self.window = UIWindow()
let vc = ViewController()
self.window!.rootViewController = vc
self.window!.makeKeyAndVisible()
self.window!.backgroundColor = .red
}
return true
}
}
SceneDelegate 파일 및 Scene 초기화 관련 로직은 오직 iOS 13 이후에서만 유효하고 Scene이 없는 환경에서는 자동적으로 무시된다. 그렇기에 AppDelegate에서만 추가적인 window 객체 초기화 코드를 실행해주면 된다.
배포 대상이 오직 iOS 13 미만일 때
기본적으로 scene을 지원하는 최신 XCode 프로젝트에서, 실제 배포 타깃이 멀티 윈도우를 지원하지 않는 iOS 13.0 미만일 경우 SceneDelegate와의 연결을 끊어야 한다. 관련된 프로세스에 대해 소개하고자 한다.
1. 프로젝트 내 SceneDelegate.swift 파일 삭제
2. AppDelegate.swift 내에서 scene과 연결되는 메소드 삭제
// scene과 연결되어 있는 해당 섹션 삭제
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
3. AppDelegate.swift 상단에 해당 코드 추가
var window: UIWindow?
→ 기존 scene에 종속되었던 window 객체를 다시 AppDelegate로 가져오는 역할을 함.
4. Info.plist 에서 Application Scene Manifest 삭제
→ scene과 관련된 메타데이터를 제거.
iOS 13 이후의 단일 윈도우 환경에서는?
단일 윈도우 환경에서는, 이전과 같이 AppDelegate 단일 파일로 모든 생애주기 메소드를 처리하는 방식이 정상 작동한다.
그러나 iOS 13부터 도입된 Scene 기반 구조는 단순히 코드 내부의 처리 방식 변경이 아니라, iOS 시스템 아키텍처 자체에서 다중 윈도우 및 앱 라이프사이클 관리를 지원하기 위해 설계된 시스템 레벨의 기능이다.
시스템에 최적인 방식이자, 아이패드 등에서의 멀티 윈도우 환경을 추후에 도입할 계획이 있다면 SceneDelegate와 AppDelegate를 분리하여 생애주기 로직을 구성하는 것이 이상적인 방식이다.
그러나 앞으로도 멀티 윈도우 환경을 채택할 계획이 없다면, AppDelegate에서 개발하던 기존 환경을 유지하는 것도 좋은 대안으로 보인다.
'앱 개발 > iOS' 카테고리의 다른 글
[iOS/Swift] Closure 정리 : 기초 문법(1/2) (0) | 2025.04.01 |
---|---|
[iOS] 딥 링킹 환경에서 콘솔 출력하기 (0) | 2025.03.25 |
[iOS] 원격 푸시 알림 정리: FCM, APNs (0) | 2025.03.25 |
[iOS] XCode 단축키 VSCode처럼 설정하기 (1) | 2025.02.22 |