-
UIKit과 SwiftUI의 생명주기 관리Apple🍎 2024. 6. 7. 14:18
앱 생명주기
어플리케이션 생명주기란 앱이 실행부터 종료까지의 과정 속에서 상태 변화를 다루는 개념이다.
Not Running (실행되지 않음)
: 앱이 아직 시작되지 않았거나 완전히 종료된 상태를 의미
Inactive (비활성 상태)
: 앱이 실행중이지만 이벤트를 받지 않는 상태(사용자와의 상호작용이 없는 상태)
- Launch Screen이 보여지는 상태 (앱이 실행되고 있지만 아직 어떤 상호작용도 불가능한 상태)
- 걸려온 전화를 눌러 전체화면으로 바꾼 상태
- 잠금화면을 내린 상태
- 제어 센터를 열었을 때
Active (활성 상태)
: 앱이 실행중이며 이벤트를 받는 상태 (사용자와의 상호작용이 가능한 상태)
- 전화가 왔지만 아직 배너 상태에서는 앱과의 상호작용이 가능한 상태임
Background ( 백그라운드 상태)
: 앱이 화면에 표시되지 않으며 대부분의 작업이 중지된 상태 (사용자와의 상호작용이 불가능)
- 백그라운드로 전환되면 몇초간 작업을 수행할 수 있는 상태가 되어 현재 실행중이던 작업을 저장하거나 더이상 필요없는 자원을 해제한다.
- 음악재생, 위치업데이트, 파일 다운로드, 건강 및 피트니스 관련 활동 추적과 같이 백그라운드에서도 지속적으로 실행되어야하는 작업이 있는 경우 제한적으로 백그라운드 작업을 요청할 수 있다.
Suspended(일시 중지 상태)
: 앱이 백그라운드에 있지만, 실행되지 않는 상태이다. 메모리에서 유지되지만 코드를 실행하지는 않는다. 메모리 부족시 시스템에 의해 앱이 종료될 수 있다.
Foreground vs Background
- 앱 생명주기 상의 각 상태를 다음과 같이도 분류할 수 있다.
- Foreground : 어플리케이션이 기기의 시스템상에서의 화면 스택의 상단에 위치한 상태
- Background : 어플리케이션이 기기의 시스템상에서의 화면 스택 하단에 위치한 상태
UIKit에서 앱 생명주기 관리 방법
AppDelegate와 SceneDelegate
- iOS 12 이전 까지는 AppDelegate가 Process Lifecycle(앱이 실행되고(메모리에 올리고), 앱이 종료되고(메모리에서 내리고)) 하는 앱 자체의 생명주기와 함께 UI Lifecycle ( 화면이 사용자에게 보여지기 시작하고, 상호작용을 시작하는 등) 앱의 User Interface에 관한 생명주기를 함께 관리 했다.
- iOS 13이후부터는 기존에 AppDelegate가 담당하던 UI Lifecycle과 관련된 부분을 SceneDelegate가 맡게 되었다.
- iOS 12 이전까지는 대부분 하나의 앱은 하나의 window를 가졌으나 iOS 13부터 Scene 개념을 도입하면서 다음과 같이 하나의 앱이 여러 window를 가질 수 있게 되었다. -> 하나의 메모 앱에서 두가지 window가 화면상에 나타나 서로 다른 UI LifeCycle을 가져 갈 수 있음
Scene이란?
기존에는 하나의 앱에는 하나의 Window를 가지고 있고 앱을 실행하면서 화면에 나타날 UI 요소들은 Window위에 띄우는 방식으로 앱을 구성했다.
- 하나의 UIWindowScene 인스턴스에는 하나의 UI 인스턴스를 나타내는 window와 viewController가 들어있다.
- 각 Scene은 대응하는 UIWindowSceneDelegate를 인스턴스를 가지고 있고 이를 통해 각 Scene을 통해 나타나는 UILifeCycle을 조정할 수 있다.
- Scene개념을 도입함으로써 하나의 앱은 여러 Scene과 SceneDelegate를 동시에 활성화 함으로써 하나의 앱 내에서도 여러 화면을 가질 수 있게 되었다.
Scene Session 이란?
- Scene 개념의 도입으로 인해 AppDelegate에서 담당하던 UI LifeCycle을 SceneDelegate가 맡게됨으로써 AppDelegate에서는 SceneSession을 통해 각 Scene에 대한 UI LifeCycle 정보를 전해받는다.
- UISceneSession 객체는 런타임시 각 scene 인스턴스를 관리할 수 있도록 해준다.
- 사용자가 앱에서 새로운 scene을 추가하거나 앱 내부적으로 새로운 scene을 요청하는 경우 (예: 메모 앱에서 새로운 메모 작성 화면을 연 경우) 시스템은 scene을 생성하고 해당 scene을 추적하는 session 객체를 생성한다.
- session에는 scene의 고유 식별자와 구성 세부사항이 들어있다.
- UIKit은 session 정보를 대응하는 scene 자체의 생명 주기동안 유지하고 scene의 생명주기가 종료된 경우( 해당 window창을 종료시킨 경우) ,그 session 또한 파괴한다.
그럼 이제 AppDelegate는 무엇을 하는가?
이제 UILifeCycle에 대한 부분을 SceneDelegate가 담당함으로써 이제 AppDelegate는 하나의 UI에 종속되지 않고 앱 전체에 대한 시스템적 생명주기 관리에 집중할 수 있게 되었다.
- 앱 실행시 필요한 데이터 구조를 초기화
- 앱 내에 사용되는 각 Scene에 대한 환경설정
- 앱 외부에서 발생한 알림(네트워크 유실, 배터리 부족, 전화 수신, 메시지 알람)등에 대응
- 특정 scene,view,view controller에 한정되지 않고 앱 자체에 대한 이벤트에 대응
- 애플 Notification과 같이 앱 실행시 요구되는 모든 서비스에 등록
정리
AppDelegate
AppDelegate
는 애플리케이션의 전체적인 생명 주기를 관리하는 역할을 수행합니다. 앱이 시작되고 종료될 때, 그리고 중요한 상태 변화가 발생할 때 호출되는 메서드를 포함하고 있습니다.AppDelegate 사용 예
AppDelegate
는 애플리케이션의 전체적인 생명 주기를 관리하는 데 사용됩니다. 예를 들어, 앱이 처음 시작될 때 초기 설정을 하거나, 앱이 완전히 종료되기 전에 데이터를 저장하는 등의 작업을 처리합니다.import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? // 앱이 시작될 때 호출됩니다. func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { print("AppDelegate: 앱이 시작됨") // 초기 설정 작업 return true } // 앱이 백그라운드로 전환될 때 호출됩니다. func applicationDidEnterBackground(_ application: UIApplication) { print("AppDelegate: 앱이 백그라운드로 전환됨") // 데이터 저장 작업 } // 앱이 포그라운드로 돌아오기 직전에 호출됩니다. func applicationWillEnterForeground(_ application: UIApplication) { print("AppDelegate: 앱이 포그라운드로 전환됨") // 상태 복원 작업 } // 앱이 종료되기 직전에 호출됩니다. func applicationWillTerminate(_ application: UIApplication) { print("AppDelegate: 앱이 종료됨") // 종료 전에 필요한 작업 수행 } }
SceneDelegate
SceneDelegate
는 하나 이상의 UI 윈도우(씬)와 관련된 생명 주기를 관리합니다. iOS 13부터는 앱이 멀티 윈도우를 지원할 수 있게 되어, 각 윈도우는 별도의 씬으로 관리됩니다.SceneDelegate
는 개별 씬의 활성화, 비활성화, 포어그라운드 전환 및 백그라운드 전환을 처리합니다.SceneDelegate 사용 예
SceneDelegate
는 각 씬의 생명 주기를 관리합니다. 이는 특히 멀티 윈도우 지원을 위해 사용됩니다. 예를 들어, 사용자가 앱 내에서 새로운 윈도우를 열 때, 해당 윈도우와 관련된 생명 주기를 관리합니다.import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? // 씬이 생성될 때 호출됩니다. func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { print("SceneDelegate: 씬이 연결됨") guard let _ = (scene as? UIWindowScene) else { return } } // 씬이 활성화될 때 호출됩니다. func sceneDidBecomeActive(_ scene: UIScene) { print("SceneDelegate: 씬이 활성화됨") // 활성화 상태에서 필요한 작업 수행 } // 씬이 비활성화되기 직전에 호출됩니다. func sceneWillResignActive(_ scene: UIScene) { print("SceneDelegate: 씬이 비활성화됨") // 비활성화 상태에서 필요한 작업 수행 } // 씬이 백그라운드로 전환될 때 호출됩니다. func sceneDidEnterBackground(_ scene: UIScene) { print("SceneDelegate: 씬이 백그라운드로 전환됨") // 백그라운드 상태에서 필요한 작업 수행 } // 씬이 포그라운드로 돌아오기 직전에 호출됩니다. func sceneWillEnterForeground(_ scene: UIScene) { print("SceneDelegate: 씬이 포그라운드로 전환됨") // 포그라운드 상태로 돌아오기 전에 필요한 작업 수행 } }
요약
- AppDelegate는 애플리케이션의 전체 생명 주기를 관리합니다. 앱의 시작, 종료, 백그라운드 전환, 포그라운드 전환 등의 중요한 상태 변화를 처리합니다.
- SceneDelegate는 개별 씬(윈도우)의 생명 주기를 관리합니다. 씬의 생성, 활성화, 비활성화, 백그라운드 전환, 포그라운드 전환 등의 상태 변화를 처리합니다. 이는 특히 멀티 윈도우 지원을 위해 도입되었습니다.
iOS 13 이후 멀티 윈도우 지원이 도입되면서
SceneDelegate
가 추가되었으며, 이는 앱의 유연한 상태 관리를 가능하게 합니다.AppDelegate
는 여전히 애플리케이션의 중요한 상태 변화를 처리하며,SceneDelegate
는 개별 윈도우의 상태 변화를 처리하는 역할을 합니다.SwiftUI에서의 앱 생명주기 관리 방법
- 명령형 패러다임을 따르는 UIKit과는 달리 SwiftUI는 선언적 패러다임을 채택하였다.
- UIKit에서는 각 생명주기에 대응하기 위해서는 각 시점에 맞는 delegate 메서드들을 선택해서 구현해주어야 했다면 SwiftUI에서는 상태를 선언하면 상태 변화시 이에 대응하는 동작을 실행할 수 있다.
SwiftUI에서 @Environment를 통한 접근
SwiftUI에서는 @Environment를 사용하여 애플리케이션의 환경 설정에 접근할 수 있다. @Environment는 SwiftUI가 제공하는 시스템 환경 값을 읽을 수 있게 해주는 속성 래퍼이다. 이를 통해 앱의 생명 주기 상태나 기타 환경 정보를 쉽게 접근할 수 있다.
import SwiftUI @main struct YourApp: App { @Environment(\.scenePhase) private var scenePhase var body: some Scene { WindowGroup { ContentView() } .onChange(of: scenePhase) { newScenePhase in switch newScenePhase { case .active: print("앱이 활성 상태입니다.") case .inactive: print("앱이 비활성 상태입니다.") case .background: print("앱이 백그라운드 상태입니다.") @unknown default: print("알 수 없는 상태입니다.") } } } } struct ContentView: View { var body: some View { Text("Hello, World!") .padding() } }
- @Environment(.scenePhase)를 사용하여 현재 scenePhase 값을 읽고, .onChange(of:)를 사용하여 상태 변화에 대응 할 수 있게 되었다.
- Suspended와 Background를 나누지 않는 이유
- SwiftUI는 앱의 생명 주기를 단순화하여 개발자가 중요한 상태 전환에만 집중할 수 있도록 설계되었다.
- background 상태는 애플리케이션이 사용자와 상호작용하지 않는 모든 경우를 포괄하며, suspended 상태는 개발자가 직접 처리할 필요가 없는 상태로 취급한다.
- suspended 상태는 시스템에 의해 관리되며, 개발자는 백그라운드 상태에서 작업을 완료하고 정리 작업을 수행하는 데 집중하면 됩니다. scenePhase는 이러한 단순화된 접근 방식을 반영하여 active, inactive, background 상태만 제공한다.
SwiftUI에서 UIKit의 앱 생명주기 관리방법이 필요한 경우
1. 푸시 알림 설정 및 처리
푸시 알림을 설정하고 디바이스 토큰을 관리하기 위해
UIApplicationDelegate
가 필요합니다.2. 외부 URL 처리
앱이 외부 URL을 통해 열릴 때 이를 처리하기 위해
UIApplicationDelegate
가 필요합니다.3. 백그라운드 작업 관리
앱이 백그라운드 상태로 전환되거나 백그라운드에서 특정 작업을 수행해야 할 때
UIApplicationDelegate
가 필요합니다.4. SceneDelegate 사용
iOS 13 이상에서는 멀티 윈도우 지원을 위해
SceneDelegate
를 사용하는 경우도 있습니다.- SwiftUI에서 UIKit의 라이프 사이클 관리 방식을 이용하기 위해
@UIApplicationDelegateAdaptor
를 사용한다. - AppDelegate class를 선언하고 해당 클래스로 관리할 생명주기 메서드들을 구현한다음에
@UIApplicationDelegateAdapter
를 통해 이를 사용사용 예제: SwiftUI에서 UIKit 라이프 사이클 관리
import SwiftUI import UserNotifications @main struct YourApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { WindowGroup { ContentView() } } } class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let center = UNUserNotificationCenter.current() center.delegate = self application.registerForRemoteNotifications() return true } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) } let token = tokenParts.joined() print("Device Token: \(token)") // 서버로 디바이스 토큰 전송 } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { print("Failed to register for remote notifications: \(error.localizedDescription)") } func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { completionHandler([.alert, .sound]) } func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { // 원격 알림 클릭 시 수행할 작업 completionHandler() } }
참고
'Apple🍎' 카테고리의 다른 글
SwiftUI와 UIKit 뷰 구성하는 법 비교하기 ( 선언형, 절차형 패러다임 비교) (0) 2024.09.19 String Catalog를 이용한 Localization (0) 2024.08.26