앱의 생명 주기
앱이 최초 실행될 때부터 앱이 종료될 때까지 앱은 여러 상태를 가집니다.
많이 들어보셨을 background, foreground도 앱의 생명 주기 중 하나이죠.
참고로 앱의 생명 주기는 공식 문서에 너무 자세히, 정확하게 서술되어 있습니다.
해당 포스팅과는 별개로 공식 문서는 꼭 읽어보시길 권장드립니다.
앱의 생명 주기는 왜 알아야 할까?
앱의 상태는 현재 수행 가능한 동작과 수행 불가능한 동작을 결정짓습니다.
Foreground 상태인 앱은 화면을 점유하고 있기 때문에 시스템 리소스보다 높은 우선순위를 가지고 있습니다.
Background 상태인 앱은 최소한의 작업을 수행해야 합니다.
앱의 상태에 따라 그에 맞는 동작을 수행시켜야 하기 때문에 앱의 생명 주기를 파악하고 있어야 합니다.
앱의 생명 주기를 알아야 구현할 수 있는 기능도 존재합니다.
예를 들어 은행 앱들은 앱에서 벗어나면 보안 화면을 보여주는 경우가 많습니다.
Background 상태에서 언제든지 종료될 수 있다는 것을 모르면 Background 기능 개발이 당황스러움의 연속일 것입니다.
앱이 화면에 보일 때만 타이머를 동작시키고 싶을 때도 앱의 생명 주기에 대해 알아야 합니다.
이렇게 앱의 생명 주기는 배워두면 유용한 지식입니다.
App-Based Life Cycle (~ iOS 12)
오늘은 iOS 13부터의 생명 주기를 집중해서 알아볼 예정입니다. (카카오톡도 iOS 13인데...)
iOS 12까지의 생명 주기는 UIApplicationDelegate가 전담하여 관리했습니다.
App-Based인 이유는 앱 단위로 상태 변화를 관리하기 때문입니다.
따라서 하나의 앱에 대해서 하나의 window만 가지게 됩니다.
iOS 12까지의 앱 생명 주기 흐름도입니다.
앱 실행 후 시스템은 화면에 UI가 표시되는지에 따라 앱을 Inactive나 Background로 변경합니다.
Foreground 상태가 될 때 시스템은 앱을 자동적으로 Active로 변경합니다.
Background나 Suspended 상태에서는 메모리가 부족해지면 언제든지 Not Running 상태로 전환될 수 있습니다.
App-Based Life Cycle은 이렇게 간단하게만 알아보았습니다.
더 자세한 내용은 공식 문서에게 패스~
Scene-Based Life Cycle (iOS 13 ~)
이제는 iOS 13부터 변화된 Scene-Based Life Cycle에 대해 알아보겠습니다.
iOS 13부터는 SceneDelegate가 추가되면서 UISceneDelegate가 앱의 생명 주기 이벤트를 관리하게 됐습니다.
App 단위의 상태 변화 관리에서 Scene 단위로 상태 변화를 관리합니다.
window라는 개념이 scene 개념으로 바뀌고 하나의 앱이 여러 scene을 가질 수 있게 되었습니다.
이는 iPad에서 Split View 화면 양쪽에 동시에 열어놓고 사용할 수 있게 되었다는 의미입니다. (iPad는 iPadOS지만요 ㅎ...)
추가로 scene 간의 동기화를 위해 UISceneSession의 개념이 AppDelegate에 추가되었습니다.
SceneDelegate를 이용한 iOS 13 이후의 앱 생명 주기 흐름도입니다.
- UIKit이 Scene 앱에 연결할 때 Scene의 초기 UI를 구성하고 Scene에 필요한 데이터를 로드합니다.
- foreground-active 상태로 전환할 때 UI를 구성하고 사용자와 상호 작용할 준비를 합니다.
- foreground-active 상태에서 벗어나면 데이터를 저장하고 앱의 동작을 조용하게 합니다.
조용하게는 어떤 의미일까 생각해보았는데 아무 동작도 하지 않게 한다는 의미 같습니다. - background 상태로 진입하면 중요한 작업을 완료하고 가능한 많은 메모리를 확보한 후 앱 스냅샷을 준비합니다.
- Scene과 연결 해제되면 Scene과 관련된 모든 공유 리소스를 정리합니다.
Scene 상태는 UIScene에 열거형으로 구현되어 있으며 공식 문서에도 소개되어 있습니다. (링크는 최하단 참고에)
하나하나 같이 알아봅시다.
Unattached
Unattached 상태는 Scene이 앱과 연결되어 있지 않은 상태입니다.
Scene은 Unattached 상태에서 시작되며 시스템이 Scene에 연결 알림을 보낼 때까지 해당 상태로 유지됩니다.
Foreground - Inactive
Foreground Inactive 상태는 실행 중이지만 이벤트를 수신하지 않는 상태입니다.
다른 상태로 이동하는 동안 foregroundInactive 상태를 지나갑니다.
위 흐름도에서도 Unattached -> Foreground Active로 변경되는 사이와 Foreground Active -> Background로 변경되는 사이에 Foreground Inactive가 배치된 것을 볼 수 있습니다.
시스템 알람, 제어센터 내리기, App-switching 등이 예시입니다.
Foreground - Active
Foreground Active 상태는 실행 중이면서 이벤트를 수신 중인 상태입니다.
active scene은 화면에 표시되며 사용자가 볼 수 있습니다.
Background
Background 상태는 실행 중이지만 화면에는 없는 상태입니다.
"실행 중"이지만 보이지 않는 것이 핵심 내용입니다.
Background에서는 음악 재생 같은 앱이 아니라면 가능한 최소한의 동작을 수행해야 하는데 아무것도 안 하는 게 제일 좋습니다.
왜냐하면 특수한 앱(음악 재생 앱 등)을 제외하면
메모리가 부족할 때 언제든지 Background 또는 Suspended Scene의 연결을 끊어서
리소스를 회수하여 Unattached 상태로 바꿀 수 있기 때문입니다.
(백그라운드 기능에 대한 심사도 엄격합니다..)
Suspended
Suspended 상태는 공식 문서에는 소개되어 있지 않습니다.
앱이 background 상태에서 추가적인 작업을 하지 않으면 곧바로 suspended 상태로 진입합니다.
앱을 다시 실행할 경우 빠른 실행을 위해 메모리에만 올라가 있습니다.
다만, 메모리가 부족할 때 언제든지 Unattached 상태가 될 수 있습니다.
SceneDelegate Method
SceneDelegate Life Cycle을 확인하기 전 어떤 메서드가 존재하는지 간단하게 알아봅시다.
메서드 순서는 공식 문서에 기재되어 있는 순서입니다.
Connecting and Disconnecting the Scene
scene(_: willConnectTo: options:)
scene이 앱에 추가될 때 호출되는 함수입니다.
사용자 또는 앱이 사용자 인터페이스의 새 인스턴스를 요청하면 UIKit이 적절한 장면 개체를 만들어 앱에 연결합니다.
이 방법을 사용하여 새 씬(scene) 추가에 응답하고 씬(scene)이 표시해야 하는 모든 데이터를 로드하기 시작합니다.
단, ViewController를 사용할 때 viewDidLoad()가 호출되는 단계는 아닙니다.
sceneDidDisconnect(_:)
Scene이 시스템에 의해 종료되었을 때 호출됩니다.
scene이 메모리에서 제거되기 전에 최종 정리를 수행합니다.
Transitioning to the Foreground
sceneWillEnterForeground(_:)
Scene을 foreground로 이동하기 전에 호출됩니다.
새로 만든 Scene과 연결된 Scene 모두에서 발생하며,
백그라운드에서 실행 중이고 시스템 또는 사용자 작업에 의해 맨 앞으로 가져온 Scene에서도 발생합니다.
Scene이 화면에 나타나기 위한 전조로 foreground에 들어가기 때문에
이 메서드는 항상 sceneDidBecomeActive(_:) 메서드를 호출합니다.
sceneDidBecomeActive(_:)
Scene이 Inactive 상태에서 Active 상태로 이동하면 호출됩니다.
UIKit은 Scene에 대한 인터페이스를 로드한 후 해당 인터페이스가 화면에 나타나기 전에 이 메서드를 호출합니다.
View를 refresh 하거나 타이머를 시작하거나 UI의 프레임 속도를 높일 때 이 메서드에 작성하면 됩니다.
Transitioning to the Background
sceneWillResignActive(_:)
Scene이 Active 상태에서 Inactive 상태로 이동할 때 호출됩니다.
이 메서드는 일시적인 중단(전화가 걸려올 때 등)으로 인해 발생할 수 있습니다.
sceneDidEnterBackground(_:)
Scene이 foreground에서 background로 전환될 때 호출됩니다.
Scene의 메모리 사용을 줄이고 공유 리소스를 확보하며 Scene의 사용자 인터페이스를 정리할 수 있습니다.
이 메서드가 반환된 직후 UIKit은 앱 전환기에 표시할 씬(scene) 인터페이스의 스냅샷을 생성합니다.
SceneDelegate Life Cycle 확인하기
SceneDelegate의 메서드들에 print를 찍어 생명 주기를 직접 확인해 봅시다.
SceneDelegate의 각 메서드 안애 print를 찍었습니다.
SceneDelegate 안에는 설명 주석이 자세히 적혀 있으므로 읽어 보시면 좋습니다.
테스트는 최대한 다양한 케이스를 아래 순서로 진행했습니다.
혹시 다른 궁금한 케이스가 있으면 댓글로 알려주세요 ㅎ
- 앱이 처음 실행될 때
- 홈 화면으로 이동할 때
- 홈에서 다시 앱을 실행할 때
- 제어 센터를 내렸을 때
- 제어 센터를 올렸을 때
- 앱 스위칭을 할 때
- 다른 앱에서 앱 스위칭으로 앱을 실행할 때
- 앱을 종료할 때
앱이 처음 실행될 때
Scene이 앱에 추가되는 순간이기 때문에 willConnectTo( )가 호출됩니다.
foreground로 이동하기 전에 sceneWillEnterForeground( )이 호출되고
바로 뒤에 Inactive 상태에서 Active 상태로 변하면서 sceneDidBecomeActive( )가 호출됩니다.
홈 화면으로 이동할 때
시뮬레이터의 홈키를 눌렀을 때 호출되는 메서드입니다.
Active 상태에서 Inactive 상태로 변하므로 sceneWillResignActive( )가 호출됩니다.
그 후 background로 전환되면서 sceneDidEnterBackground( )가 호출됩니다.
홈에서 다시 앱을 실행할 때
홈에서 앱 아이콘을 눌러 실행시켰을 때 호출되는 메서드입니다.
Foreground로 돌아오면서 sceneWillEnterForeground( )가 호출됩니다.
Inactive 상태에서 Active 상태로 변경되면서 sceneDidBecomeActive( )가 호출됩니다.
제어 센터를 내렸을 때
앱이 켜져 있을 때 제어 센터를 내린 뒤 호출되는 메서드입니다.
Active에서 Inactive로 바뀌었기 때문에 sceneWillResignActive( )가 호출됩니다.
Background로 간 것은 아니기 때문에 sceneWillEnterForeground( )는 호출되지 않습니다.
제어 센터를 올렸을 때
제어 센터를 올렸을 때 호출되는 메서드입니다.
Inactive에서 Active로 바뀌었으므로 sceneDidBecomeActive( )가 호출됩니다.
마찬가지로 Background에서 Foreground로 온 게 아니기 때문에 sceneWillEnterForeground( )는 호출되지 않습니다.
앱 스위칭을 할 때
앱 안에서 앱 전환 화면(홈 키를 두 번 누르거나 하단 인디케이터를 위로 밀었을 때 나오는 화면)에 진입할 때 호출되는 메서드입니다.
Active에서 Inactive로 바뀌었기 때문에 sceneWillResignActive( )가 호출됩니다.
앱 스위칭 화면에서 돌아왔을 때
앱 전환 화면에서 바로 앱으로 돌아올 때 호출되는 메서드입니다.
Inactive에서 Active로 바뀌었으므로 sceneDidBecomeActive( )가 호출됩니다.
다른 앱에서 앱 스위칭으로 앱을 실행할 때
다른 앱에서 앱 스위칭으로 앱으로 돌아왔을 때 호출되는 메서드입니다.
일단 다른 앱으로 이동했기 때문에 Background 상태일 것입니다.
Background에서 Foreground로 돌아왔기 때문에 sceneWillEnterForeground( )이 호출됩니다.
Inactive에서 Active로 바뀌었으므로 sceneDidBecomeActive( )가 호출됩니다.
앱을 종료할 때
앱이 종료되면 sceneDidDisconnect( )이 호출됩니다.
마무리
오늘은 앱의 생명 주기에 대해 알아보고 8개 시나리오에 대해 호출되는 메서드를 직접 확인해 보았습니다.
예상을 벗어나는 결과가 없어서 너무나 다행이면서 한편으로는 심심했던...? 결과였네요 ㅎㅎ
오늘 공부한 앱 생명 주기를 토대로 앱에 대해 더 깊은 이해를 하고
실제 개발을 할 때 적절한 기능 구현을 하면 좋겠습니다.
감사합니다!
참고
https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
https://developer.apple.com/documentation/uikit/uiscenedelegate
https://developer.apple.com/documentation/uikit/uiapplicationdelegate
https://developer.apple.com/documentation/uikit/uiscene/activationstate
아직은 초보 개발자입니다.
더 효율적인 코드 훈수 환영합니다!
공감과 댓글 부탁드립니다.