[iOS] iOS 12 이하의 App Life Cycle과 UIApplicationDelegate

AppDelegate에서 호출되는 Life Cycle 관련 함수를 상황별 실험을 통해 확인해보자👀

naljin
17 min readNov 13, 2021

들어가기 전에

개-하! AppDelegate 에서 상태 변화 관련된 method 들 잘 아시나여??! (다짜고짜)

물론 전 모름요! ㅎㅎ 맨날 파이어베이스 설정하려면 didFinishLaunchingWithOptions 에서 설정하고 이러라길래 ㅇㅋㅇㅋ 하고 그냥 지나쳐왔던 지난 날,, 이제는 업보가 쌓여 미룰 수 없게 되었습니다.

그래서 오늘은! App의 state에는 어떤 것이 있는지, iOS 12 이전 버전에서는 이 state 변화를 어떻게 파악했는지 알아볼거예여. 그리고 실험을 통해 상황 별로 어떤 state 관련 함수가 호출되는지 살펴볼겁니다.

예고 드렸다시피 앞부분에는 이론적인 내용들을 쭉 쓰긴 할건데,, 개념은 언제나 그렇듯 공식 문서를 원문으로 보는 것을 추천드립니다 ◠‿◠

iOS App의 Life Cycle ♻️

앱의 생명 주기란 앱의 사용 과정 중 가지는 상태 값을 나타내는 것으로 크게 5개의 상태를 가집니다.

  1. Non-running(Terminated) — 아직 실행되지 않았거나, 완전히 종료된 상태
  2. Inactive(Foreground) — 앱이 Foreground에서 실행 중이지만 이벤트는 받지 못하는 상태. Active 상태로 넘어가기 전에 앱은 반드시 이 상태를 거침.
  3. Active(Foreground)— 앱이 실행 중이고 이벤트를 받을 수 있는 상태.
  4. Background — 앱 사용중에 다른 앱을 실행하거나 홈 화면으로 나갔을 때 상태. 백그라운드에서 동작하는 코드를 추가하면 suspended 상태로 넘어가지 않고 백그라운드 상태를 유지 (ex. 음악을 실행하고 홈 화면으로 나가도 음악이 나오는 상태)
  5. Suspended — 앱이 background 상태에서 추가적인 작업을 하지 않으면 곧바로 suspended 상태로 진입. 시스템에 의해 자동으로 suspended 상태로 되기 때문에, 상태 이동에 대한 알림을 따로 받지 않음. 코드는 실행되지 않으며 앱을 다시 실행할 경우 빠른 실행을 위해 메모리에만 올라가 있음. 메모리가 부족한 상황이 되면 iOS는 suspended 상태에 있는 앱들을 메모리에서 해제시켜서 메모리를 확보

앱의 상태가 변경되면 UIKit는 적절한 delegate object의 method 를 호출해서 우리에게 알려주는데, iOS 13 이상에서는 UISceneDelegate 을 사용하여 scene 기반 앱의 life cycle event에 응답하고, iOS 12 이전 버전에서는 UIApplicationDelegate 을 사용하여 life cycle event 에 응답합니다.

오늘은 iOS 12 이하의 UIApplicationDelegate 에서 어떤 메소드들을 통해 app life cycle event 에 응답하는지 살펴보도록 합시다. 대략적으로 7개가 있는 듯하네요 👀

상태 관련 UIApplicationDelegate method 🗣

우선 초기 UI를 구성하고 필요한 데이터를 로드하는 함수들이 필요하겠져?

요기 바로 두개의 함수가 있습니다.

application(_:willFinishLaunchingWithOptions:)

  • delegate에게 launch process가 시작 되었지만, state restoration은 아직 발생하지 않았음을 알립니다. (참고로 state restoration 이란, 앱이 종료되고 다시 시작될 때 이전에 보존된 데이터를 통해 view 및 view controller를 재구성하여 이전 상태로 돌아가도록 도와주는 작업을 말합니다. 자세한 내용은 이곳을 참고합시다.)
  • 이곳에서 프로그램을 초기화하고 실행할 준비를 합니다.
  • 이 메서드는 앱이 실행되고 main storyboard 또는 nib 파일이 로드된 후 호출 됩니다.
  • 이때 앱은 InActive 상태입니다.

application(_:didFinishLaunchingWithOptions:)

  • delegate에게 launch process가 거의 완료되었으며 앱 실행 준비가 거의 완료되었음을 알립니다.
  • 이곳에서 앱의 초기화를 완료하고 모든 최종 변경을 수행합니다.
  • 이 메서드는 state restoration이 발생했지만, 앱의 window와 다른 UI가 표시되기 전에 호출됩니다.
  • 이 메서드가 return 된 후, 시스템은 다른 app delegate의 메서드를 호출하여 앱을 Active(foreground) 상태 또는 Background 상태로 이동시킵니다.

이제 본격적으로 App Life Cycle 이벤트에 응답하는 delegate 함수들을 살펴봅시다

applicationWillEnterForeground(_:)

  • 앱이 Background 에서 Foreground로 진입할 것임을 delegate에게 알립니다.
  • 이를 통해 앱이 Background 상태던 앱이 InActive 상태로 전환됩니다.
  • 이곳에서 disk 로부터 리소스를 로드하고 네트워크에서 데이터를 가져옵니다.
  • 백그라운드로 진입할 때 작업한 많은 내용을 취소할 수도 있습니다.
  • 언제나 앱을 InActive 상태에서 Active상태로 이동시키는 applicationDidBecomeActive(_:)를 뒤이어 호출합니다.

applicationDidBecomeActive(_:)

  • UIKit은 프로그램이 InActive 상태에서 Active 상태로 이동했음을 알리기 위해 이 메서드를 호출합니다.
  • 앱은 사용자나 시스템에 의해 실행됨으로써 Active 상태가 될 수도 있지만, 일시적으로 앱을 InActive 상태로 만드는 interruption (수신 전화 or SMS 메시지 등) 을 무시함으로써 Active 상태로 되돌아갈 수도 있습니다.
  • 이곳에서 앱이 InActive 상태인 동안 일시 중지되었던(혹은 아직 시작되지 않은) 작업들을 재개합니다(ex. 타이머 재시작). 이전에 앱이 Background에 있었다면 앱의 사용자 인터페이스를 새로 고치는 데도 사용할 수 있습니다.
  • Activation 상황에서 적합한 작업 내용은 이곳의 Configure Your User Interface and Initial Tasks at Activation을 참고하세요

applicationWillResignActive(_:)

  • UIKit은 앱이 Active상태에서 InActive 상태로 이동한다는 것을 알리기 위해 이 메서드를 호출합니다.
  • 앱을 Background 상태로 전환하기 시작할 때나, 수신 전화나 SMS 메시지와 같은 일시적인 interruption 으로 인해 InActive 상태로 전환될 수 있습니다.
  • InActive 상태의 앱은 Active 또는 Background 상태로 전환되기를 기다리는 동안 최소한의 작업만 수행해야 합니다.
  • 이곳에서 타이머 비활성화, 게임 일시 중지 등 진행 중인 작업을 중단하고, 사용자의 데이터를 저장할 수 있습니다.
  • Deactivation 상황에서 적합한 작업 내용은 이곳의 Quiet Your App upon Deactivation을 참고하세요

applicationDidEnterBackground(_:)

  • delegate에게 앱이 Background 상태에 있음을 알립니다.
  • 앱은 여러 가지 이유로 Background 상태로 이동합니다. 사용자가 Foreground 앱을 종료하면 UIKit이 앱을 Suspend 시키기 전에 Background 상태로 잠시 이동합니다. 혹은 시스템이 앱을 Background 상태로 직접 launch할 수도 있고, suspended 된 앱을 Background로 이동시켜 중요한 작업을 수행할 시간을 줄 수 있습니다.
  • 앱이 Background에 있을 때는 가능한 적은 일을 수행해야하며, 가급적이면 아무 작업도 수행하지 않는 것이 좋습니다. 약 5초 전에 메서드가 반환되지 않으면 앱이 종료되고 메모리에서 제거됩니다.
  • 최종 작업을 수행하는 데 추가 시간이 필요한 경우 최대한 빨리 beginBackgroundTask(expirationHandler:)를 호출하여 시스템에 추가 실행 시간을 요청합니다.
  • 이곳에서 공유 리소스를 해제, 타이머 무효화 등의 작업을 수행합니다. 상태 정보(app state information)를 저장하여 나중에 앱이 종료(terminated) 되는 경우 앱을 현재 상태로 복원(restore)할 수 있습니다.
  • 백그라운드로 전환 시 적합한 작업 내용은 이곳의 Release Resources upon Entering the Background을 참고하세요.
  • 앱이 백그라운드로 들어가고 delegate method가 return되면 UIKit은 앱의 현재 사용자 인터페이스의 스냅샷을 만듭니다. 시스템은 app switcher 에서 해당 이미지를 표시하고, 앱을 다시 Foreground로 가져올 때도 일시적으로 표시합니다. 이때 비밀번호나 신용카드 번호와 같은 민감한 사용자 정보가 포함되어서는 안되므로 인터페이스에 이러한 정보가 포함되어 있으면 Background에 들어갈 때 view에서 제거해야 합니다.

오호,, 마지막 스냅샷 예시를 넷플릭스로 들 수 있을거 같은데요,,🤔 넷플릭스를 보다가 app switcher 로 전환하면 보던 영상의 스냅샷이 그대로 뜹니다. 여기서 홈 화면으로 들어가는 등 넷플릭스를 Background로 한번 보낸 다음에 다시 app switcher 로 들어가면 넷플 스냅샷은 검정 화면으로 뜨는 것을 확인할 수 있습니다. 흥미진진~

applicationWillTerminate(_:)

  • 앱이 종료될 시기를 delegate 에게 알립니다.
  • 이를 통해 앱이 메모리에서 완전히 종료 및 제거될 것임을 알 수 있습니다.
  • 공유 리소스 해제, 사용자 데이터 저장 및 타이머 무효화와 같은 앱에 대한 최종 정리 작업을 수행해야 합니다.
  • 이곳에서 작업을 수행하고 return하는 데 약 5초의 시간을 사용할 수 있습니다. 시간이 만료되기 전에 메서드가 return되지 않으면 시스템이 프로세스를 완전히 종료할 수 있습니다.

직접 실험 해보기 📱🔬

앱 시작

willFinishLaunchingWithOptions 👉🏻 didFinishLaunchingWithOptions 👉🏻applicationDidBecomeActive

순서로 delegate method 가 호출되네요. 여기서 applicationWillEnterForeground(_:) 가 호출되지 않고 바로 BecomeActive로 간다는 점이 약간의 흥미 포인트였어요.

화면잠금

applicationWillResignActive 👉🏻 applicationDidEnterBackground

음 화면을 잠그면 예상대로 Active 상태를 포기하고 (ResignActive), Background로 들어가는군여

잠금화면 해제

applicationWillEnterForeground 👉🏻 applicationDidBecomeActive

화면 잠금을 푼다면 위의 상황과는 반대로 Foreground에 진입한 뒤 Active 상태가 되겠죠?

알림 센터 / 제어 센터 내림 (노출)

applicationWillResignActive

알림 센터를 내리면 어떻게 되나 한번 실험해봤어요. 이때는 Background 까지 가지는 않고 그냥 ResignActive 만 호출되네요. 약간 일시적인 동작 느낌이라 그런걸까요?? wifi 설정 등을 위해 제어센터를 내릴때도 마찬가지였습니다.

알림 센터 / 제어 센터 올림 (해제)

applicationDidBecomeActive

반대로 알림 센터를 올려서 다시 원래 앱이 보이게 하면 Active 상태로 돌아왔습니다.

수신 전화 알림 / 알림 상태로 응답

아무 함수도 호출되지 않음 🙄

아니 원래 applicationWillResignActive(_:) 쪽 설명에서 ‘수신 전화같은 일시적인 interruption 으로 인해 InActive 상태로 전환될 수 있다’ 라고 하길래 실험을 해봤거든여. 근데 알림이 저렇게 족구맣게 떠서 그런지 제 앱이 InActive 상태로 가지는 않았어요. 물론 전화를 받았을때도 마찬가지입니다.

수신 전화 큰 화면으로 받음

applicationWillResignActive 👉🏻 applicationDidEnterBackground

조그만 알림창?에서 전화를 받았을때는 아무런 상태변화가 일어나지 않길래 큰 화면으로 전환해봤어요. 그랬더니 ResignActive 와 EnterBackground가 순서대로 호출되었습니다.

수신 전화 큰 화면으로 끊음

applicationWillEnterForeground 👉🏻 applicationDidBecomeActive

전화를 끊었을때는 위의 상황과는 반대로 Foreground에 진입한 뒤 Active 상태가 되었습니다.

카톡 알림

아무 함수도 호출되지 않음 🙄

위에서 전화 알림에 아무 함수도 호출되지 않길래, 그럼 무슨 알림이 앱을 InActive 상태로 보내냐?? 하는 생각이 들었습니다. 그래서 우선 카톡 알림을 실험해봤습니다. 결과는 역시나 아무 함수도 호출되지 않았습니다.

재난 알림

applicationWillResignActive

앱을 InActive 상태로 만드는 알림에 대해 생각하다가, 재난 알림은 기존 화면을 흐리게 덮는거 같던데?? 라고 문득 떠올랐습니다. 얘는 앱을 InActive로 만들것 같았기에 의지의 한국인은 알림 문자가 오는 시간에 맞춰 폰을 연결해 실험해봤습니다.

동영상 찍던 폰에도 재난 알림 문자가 오면서 영상이 강제 중지되어 로그가 남는 과정을 제대로 찍진 못했지만 어쨌든 ResignActive 함수가 호출되는 것을 확인할 수 있었습니다. 코로나 덕분에 재난 문자 실험도 해보네요.. 🤭

App Switcher 진입

applicationWillResignActive

App Switcher에 진입했을때는 ResignActive 만 호출되었습니다.

App Switcher에서 그냥 돌아옴

applicationDidBecomeActive

App Switcher 에서 다른 앱으로 전환하지 않고, 원래 앱으로 돌아오니 다시 BecomeActive 가 호출되네요

홈 갈까 말까,,

applicationWillResignActive

홈 화면으로 바로 진입하지 않고 갈까 말까.. 하면서 제스처를 유지하고 있는 상황은 바로 위에서 살펴본 App Switcher에 진입했을 때와 동일했습니다. ResignActive 만 호출되었습니다.

홈 갈까 말까,, 하다 돌아옴

applicationDidBecomeActive

위의 상황에서 홈으로 진입하지 않고, 다시 내 앱을 제자리에 돌려놓으면? App Switcher에서 돌아왔을 때와 동일하게 BecomeActive 가 호출되었습니다.

홈 화면 진입

applicationWillResignActive 👉🏻 applicationDidEnterBackground

물론 아예 홈 화면으로 진입해버리면 ResignActive에 이어 EnterBackground가 호출되었구요?

홈 화면에서 다시 앱 진입

applicationWillEnterForeground 👉🏻 applicationDidBecomeActive

홈에서 다시 돌아왔을 때는 반대로 Foreground에 진입한 뒤 Active 상태가 되었습니다.

앱 종료

applicationWillResignActive 👉🏻 applicationDidEnterBackground 👉🏻 applicationWillTerminate

앱을 날려서 강제 종료 시킬 때는 마지막으로 applicationWillTerminate 가 호출되며 앱이 종료되었음을 알려주었습니다.

마무리

흠냐리 흠냐,, 이제 AppDelegate 에 대해 드디어 조금은 알게 된걸까여,,?? SceneDelegate도 제대로 사용을 안해봐서 공부해야되는데,, ㅎㅎㅋ 일단~~ 넷플릭스나 보러~~ 도망 도망~~! 그럼 20000!

출처

Managing Your App’s Life Cycle

application(_:willFinishLaunchingWithOptions:)

application(_:didFinishLaunchingWithOptions:)

applicationWillEnterForeground(_:)

applicationDidBecomeActive(_:)

applicationWillResignActive(_:)

applicationDidEnterBackground(_:)

applicationWillTerminate(_:)

Preparing Your UI to Run in the Background

Preserving Your App’s UI Across Launches

About the UI Restoration Process

--

--