♻️ 앱의 라이프 사이클
Swift에서 가장 중요하게 다루는 내용 중 하나인 앱의 Life Cycle에 대해서 알아보자!
Managing your app’s life cycle | Apple Developer Documentation
Respond to system notifications when your app is in the foreground or background, and handle other significant system-related events.
developer.apple.com
앱의 라이프 사이클을 표현하는데 가장 좋은 그림은 아래이지 않을까 싶다. 앱은 크게 Not Running, Foreground, Background로 나뉘게 된다. 내 눈앞에 있으면 Foreground, 내 눈앞에 없으면 Background로 생각하면 될 것 같다.

⬛️ Not Running
실행되지 않은 상태의 앱이다.
🟩 Inactive
앱이 Active로 들어가기 전에 반드시 거쳐야 하는 관문이다. 실행은 되고 있지만 이벤트를 받지 않는 상태이다. 예를 들면 전화가 오게 되면 active → inactive 상태가 된다.
🟩 Active
실행 중이고 사용자 이벤트를 받고 있는 상태이다. Inactive로 갔다가 다시 돌아올 수도 있고, 만약 Inactive를 거쳐 Background로 진입한 뒤 작업이 있다면 Background Running, 없다면 Suspend 단계로 간다.
⬜️ Running (Background)
앱이 백그라운드에서 실행 중인 상태이다. 실행이 완료되면 Suspend 상태로 간다.
⬜️ Suspend
백그라운드에 있지만 실행이 멈추고 메모리에만 유지되어 있다. (압축되어 있다) 메모리가 부족하면 Not Running 상태가 된다.
📲 AppDelegate
iOS 12이전은 아래와 같이 AppDelegate: UIApplicationDelegate를 활용한다.
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 앱이 처음 시작될 때 호출 (Not Running → Inactive)
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Active → Inactive 직전 (곧 포커스를 잃게 될 때)
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Background 상태 진입 직후 (Inactive → Background 이후 호출됨)
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Foreground 진입 직전 (Background → Inactive 직전)
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Active 상태 진입 직후 (Inactive → Active 직후)
}
func applicationWillTerminate(_ application: UIApplication) {
// 앱 종료 직전 (Not Running 직전, 주로 Background Running → 종료)
}
📲 SceneDelegate
iOS 13 이후로 SceneDelegate로 App단위가 아닌 Scene단위로 라이프사이클을 관리하게 되었다. 여러 개의 창을 띄울 수 있게 되면서 각 윈도우(Scene)의 생명주기를 별도로 관리하게 되었다.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
// 앱 실행 시, 또는 새 scene 연결될 때 호출됨 (Not Running -> Inactive)
}
func sceneWillResignActive(_ scene: UIScene) {
// Active -> Inactive 직전
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Inactive -> Background 직후
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Background -> Inactive 직전
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Inactive -> Active 직후
}
func sceneDidDisconnect(_ scene: UIScene) {
// Background or Suspended -> Not Running 직전
}
}
🤔 궁금점
⏳ 왜 어쩔땐 Will이고 어쩔 땐 Did일까?
라이프사이클을 보니 어쩔 때는 Will이고 어쩔때는 Did로 되어있다. 왜 sceneWillResignActive는 있고, sceneDidResignActive는 없는 걸까? 왜 sceneDidEnterBackground는 있고 sceneWillEnterBackground는 없을까? 바로 준비, 처리의 차이에 있는 것 같다. Active에서 Inactive로 간다면 준비해야하고, Background로 들어갔다면 처리한다는 개념이다.
Inactive는 Background를 가기위한 위한 관문(?) 느낌이어서 그 사이를 쪼개서 관리할 필요가 없을 것이라고 생각한 것 같기도 하다. 반대로 Background에서 Foreground들어갈때(Inactive될때) 준비하고 Active가 되고 처리하는 방식으로 동작하게 된다는 것을 알았다.
참고로 아래와 같은 화면은 이벤트를 받지 못하지만 화면에 보이는 상태이므로 sceneWillResignActive(_:)만 실행된다! Inactive 상태인 것이다.

🧪 Background Running은 얼마나 실행될까?
아래의 timer와 같은 간단한 작업을 만든 후 sceneDidEnterBackground에 아래와 같은 코드를 넣어줬다.
func sceneDidEnterBackground(_ scene: UIScene) {
print(#function)
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "Timer") {
UIApplication.shared.endBackgroundTask(self.backgroundTask)
self.backgroundTask = .invalid
print("🛑 백그라운드 작업 강제 종료됨")
}
seconds = 0
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
self.seconds += 1
print("⏰ 백그라운드 실행 시간: \(self.seconds)초 경과")
}
guard let timer else { return }
RunLoop.current.add(timer, forMode: .default)
}
결과는 아래와 같이 26초 쯔음에 종료된다고 하고 5초의 시간을 더 주는 것 같아 보인다.
sceneWillEnterForeground(_:)
sceneDidBecomeActive(_:)
sceneWillResignActive(_:)
sceneDidEnterBackground(_:)
⏰ 백그라운드 실행 시간: 1초 경과
⏰ 백그라운드 실행 시간: 2초 경과
⏰ 백그라운드 실행 시간: 3초 경과
⏰ 백그라운드 실행 시간: 4초 경과
⏰ 백그라운드 실행 시간: 5초 경과
...
⏰ 백그라운드 실행 시간: 25초 경과
⏰ 백그라운드 실행 시간: 26초 경과
🛑 백그라운드 작업 강제 종료됨
⏰ 백그라운드 실행 시간: 27초 경과
⏰ 백그라운드 실행 시간: 28초 경과
⏰ 백그라운드 실행 시간: 29초 경과
⏰ 백그라운드 실행 시간: 30초 경과
⏰ 백그라운드 실행 시간: 31초 경과
만약에 UIApplication.shared.endBackgroundTask(self.backgroundTask)를 하지 않고 아래와 같이 종료를 제대로 안 시켜준다면...
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "Timer") {
print("🛑 백그라운드 작업 강제 종료됨")
}
아래와 같이 31초까지는 찍히지만 This app will likely be terminated by the system. 라는 OS의 사형선고를 볼 수 있었다.
...
⏰ 백그라운드 실행 시간: 26초 경과
🛑 백그라운드 작업 강제 종료됨
Background task still not ended after expiration handlers were called:
<_UIBackgroundTaskInfo: 0x600001737100>: taskID = 2, taskName = TimerTask, creationTime = 45022 (elapsed = 26).
This app will likely be terminated by the system.
Call UIApplication.endBackgroundTask(_:) to avoid this.
⏰ 백그라운드 실행 시간: 27초 경과
⏰ 백그라운드 실행 시간: 28초 경과
⏰ 백그라운드 실행 시간: 29초 경과
⏰ 백그라운드 실행 시간: 30초 경과
⏰ 백그라운드 실행 시간: 31초 경과
☠️ applicationWillTerminate가 되어도 작업을 할 수 있을까?
공식문서의 Discussion에 나와있는데, Your implementation of this method has approximately five seconds to perform any tasks and return. 라는 말이 있다. 약 5초 정도는 필요한 작업을 할 수 있게 해주는 것 같다.
applicationWillTerminate(_:) | Apple Developer Documentation
Tells the delegate when the app is about to terminate.
developer.apple.com
'→ UIKit' 카테고리의 다른 글
[UIKit] Sendbird 로 배우는 뷰의 라이프 사이클 (0) | 2025.04.12 |
---|
♻️ 앱의 라이프 사이클
Swift에서 가장 중요하게 다루는 내용 중 하나인 앱의 Life Cycle에 대해서 알아보자!
Managing your app’s life cycle | Apple Developer Documentation
Respond to system notifications when your app is in the foreground or background, and handle other significant system-related events.
developer.apple.com
앱의 라이프 사이클을 표현하는데 가장 좋은 그림은 아래이지 않을까 싶다. 앱은 크게 Not Running, Foreground, Background로 나뉘게 된다. 내 눈앞에 있으면 Foreground, 내 눈앞에 없으면 Background로 생각하면 될 것 같다.

⬛️ Not Running
실행되지 않은 상태의 앱이다.
🟩 Inactive
앱이 Active로 들어가기 전에 반드시 거쳐야 하는 관문이다. 실행은 되고 있지만 이벤트를 받지 않는 상태이다. 예를 들면 전화가 오게 되면 active → inactive 상태가 된다.
🟩 Active
실행 중이고 사용자 이벤트를 받고 있는 상태이다. Inactive로 갔다가 다시 돌아올 수도 있고, 만약 Inactive를 거쳐 Background로 진입한 뒤 작업이 있다면 Background Running, 없다면 Suspend 단계로 간다.
⬜️ Running (Background)
앱이 백그라운드에서 실행 중인 상태이다. 실행이 완료되면 Suspend 상태로 간다.
⬜️ Suspend
백그라운드에 있지만 실행이 멈추고 메모리에만 유지되어 있다. (압축되어 있다) 메모리가 부족하면 Not Running 상태가 된다.
📲 AppDelegate
iOS 12이전은 아래와 같이 AppDelegate: UIApplicationDelegate를 활용한다.
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 앱이 처음 시작될 때 호출 (Not Running → Inactive)
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Active → Inactive 직전 (곧 포커스를 잃게 될 때)
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Background 상태 진입 직후 (Inactive → Background 이후 호출됨)
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Foreground 진입 직전 (Background → Inactive 직전)
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Active 상태 진입 직후 (Inactive → Active 직후)
}
func applicationWillTerminate(_ application: UIApplication) {
// 앱 종료 직전 (Not Running 직전, 주로 Background Running → 종료)
}
📲 SceneDelegate
iOS 13 이후로 SceneDelegate로 App단위가 아닌 Scene단위로 라이프사이클을 관리하게 되었다. 여러 개의 창을 띄울 수 있게 되면서 각 윈도우(Scene)의 생명주기를 별도로 관리하게 되었다.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
// 앱 실행 시, 또는 새 scene 연결될 때 호출됨 (Not Running -> Inactive)
}
func sceneWillResignActive(_ scene: UIScene) {
// Active -> Inactive 직전
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Inactive -> Background 직후
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Background -> Inactive 직전
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Inactive -> Active 직후
}
func sceneDidDisconnect(_ scene: UIScene) {
// Background or Suspended -> Not Running 직전
}
}
🤔 궁금점
⏳ 왜 어쩔땐 Will이고 어쩔 땐 Did일까?
라이프사이클을 보니 어쩔 때는 Will이고 어쩔때는 Did로 되어있다. 왜 sceneWillResignActive는 있고, sceneDidResignActive는 없는 걸까? 왜 sceneDidEnterBackground는 있고 sceneWillEnterBackground는 없을까? 바로 준비, 처리의 차이에 있는 것 같다. Active에서 Inactive로 간다면 준비해야하고, Background로 들어갔다면 처리한다는 개념이다.
Inactive는 Background를 가기위한 위한 관문(?) 느낌이어서 그 사이를 쪼개서 관리할 필요가 없을 것이라고 생각한 것 같기도 하다. 반대로 Background에서 Foreground들어갈때(Inactive될때) 준비하고 Active가 되고 처리하는 방식으로 동작하게 된다는 것을 알았다.
참고로 아래와 같은 화면은 이벤트를 받지 못하지만 화면에 보이는 상태이므로 sceneWillResignActive(_:)만 실행된다! Inactive 상태인 것이다.

🧪 Background Running은 얼마나 실행될까?
아래의 timer와 같은 간단한 작업을 만든 후 sceneDidEnterBackground에 아래와 같은 코드를 넣어줬다.
func sceneDidEnterBackground(_ scene: UIScene) {
print(#function)
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "Timer") {
UIApplication.shared.endBackgroundTask(self.backgroundTask)
self.backgroundTask = .invalid
print("🛑 백그라운드 작업 강제 종료됨")
}
seconds = 0
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
self.seconds += 1
print("⏰ 백그라운드 실행 시간: \(self.seconds)초 경과")
}
guard let timer else { return }
RunLoop.current.add(timer, forMode: .default)
}
결과는 아래와 같이 26초 쯔음에 종료된다고 하고 5초의 시간을 더 주는 것 같아 보인다.
sceneWillEnterForeground(_:)
sceneDidBecomeActive(_:)
sceneWillResignActive(_:)
sceneDidEnterBackground(_:)
⏰ 백그라운드 실행 시간: 1초 경과
⏰ 백그라운드 실행 시간: 2초 경과
⏰ 백그라운드 실행 시간: 3초 경과
⏰ 백그라운드 실행 시간: 4초 경과
⏰ 백그라운드 실행 시간: 5초 경과
...
⏰ 백그라운드 실행 시간: 25초 경과
⏰ 백그라운드 실행 시간: 26초 경과
🛑 백그라운드 작업 강제 종료됨
⏰ 백그라운드 실행 시간: 27초 경과
⏰ 백그라운드 실행 시간: 28초 경과
⏰ 백그라운드 실행 시간: 29초 경과
⏰ 백그라운드 실행 시간: 30초 경과
⏰ 백그라운드 실행 시간: 31초 경과
만약에 UIApplication.shared.endBackgroundTask(self.backgroundTask)를 하지 않고 아래와 같이 종료를 제대로 안 시켜준다면...
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "Timer") {
print("🛑 백그라운드 작업 강제 종료됨")
}
아래와 같이 31초까지는 찍히지만 This app will likely be terminated by the system. 라는 OS의 사형선고를 볼 수 있었다.
...
⏰ 백그라운드 실행 시간: 26초 경과
🛑 백그라운드 작업 강제 종료됨
Background task still not ended after expiration handlers were called:
<_UIBackgroundTaskInfo: 0x600001737100>: taskID = 2, taskName = TimerTask, creationTime = 45022 (elapsed = 26).
This app will likely be terminated by the system.
Call UIApplication.endBackgroundTask(_:) to avoid this.
⏰ 백그라운드 실행 시간: 27초 경과
⏰ 백그라운드 실행 시간: 28초 경과
⏰ 백그라운드 실행 시간: 29초 경과
⏰ 백그라운드 실행 시간: 30초 경과
⏰ 백그라운드 실행 시간: 31초 경과
☠️ applicationWillTerminate가 되어도 작업을 할 수 있을까?
공식문서의 Discussion에 나와있는데, Your implementation of this method has approximately five seconds to perform any tasks and return. 라는 말이 있다. 약 5초 정도는 필요한 작업을 할 수 있게 해주는 것 같다.
applicationWillTerminate(_:) | Apple Developer Documentation
Tells the delegate when the app is about to terminate.
developer.apple.com
'→ UIKit' 카테고리의 다른 글
[UIKit] Sendbird 로 배우는 뷰의 라이프 사이클 (0) | 2025.04.12 |
---|