Jaebi의 Binary는 호남선

App Lifecycle 본문

Swift

App Lifecycle

jaebijae 2024. 7. 21. 18:46

목차

    iOS App Lifecycle

    • iOS 앱의 생명주기는 크게 3가지로 분류
    • Not Running → 앱이 실행되고 있지 않은 상태
    • Foreground → 앱이 실행되어 화면을 차지 하고 있는 상태
      • InActive → 외부적인 방해로 Full Control 불가능
      • Active → 앱이 화면을 차지하고 있으면서 앱을 Full Control 가능
      • Active 상태가 되거나 벗어날때는 InActive를 반드시 거쳐가야함
    • Background → 앱이 메모리를 차지하고 있지만 화면을 차지하지 않은 상태
      • Running → 화면을 차지하지 않더라도 Background에서 게속 작업을 수행
      • Suspend → 앱이 메모리에서 아무 행동을 하지 않으면서 사용자가 앱을 다시 실행할 때까지 대기
    Flow 예시 상황
    Not Running → InActive → Active 앱 실행 하여 초기 페이지 진입
    Active → InActive 앱이 켜져있을때 앱 스위처로 이동
    Active → InActive → Running 음악 플레이어 켜져있을때 체스처로 플레이어 백그라운드로 보냄
    Running → Not Running (Timeout) 앱이 background에서 작업을 정해진 시간 내에 완료 못하면 Timeout 함수 호출, Not Running 상태로 됨
    Running → Suspend 앱이 background에서 작업을 정해진 시간 내에 완료하면 Suspend 상태로 됨
    Active → InActive → Suspend 음악 플레이어를 정지하고 체스처로 플레이어 백그라운드로 보냄
    Suspend → Not Running (메모리 부족) Suspend상태에서 메모리 용량이 부족하면 OS의 결정으로 앱이 메모리에서 내려가 Not Running 상태로 됨
    Running → Not Running
    InActive → Not Running
    Background에 있는 앱 종료
    앱 스위처에서 드래그 제스처로 종료

    SwiftUI에서 App Lifecycle 확인

    • `@Environment` property wrapper를 사용하여 `ScenePhase`를 가져오고 `onChange(of:)` modifier로 상태 확인
    struct ScenePhaseApp: App {
        @Environment(\.scenePhase) var scenePhase
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
            .onChange(of: scenePhase) { newScenePhase in
                switch newScenePhase {
                case .active:
                    print("active")
                case .inactive:
                    print("inactive")
                case .background:
                    print("background")
                @unknown default:
                    print("unknown")
                }
            }
        }
    }
    • `NotificationCenter` → 등록된 event가 발생하면 해당 event에 대한 행동을 취함
    • `NotificationCenter`를 사용하여 `UIApplication`을 observe
    struct NotificationCenterApp: App {
        var body: some Scene {
            WindowGroup {
                ContentView()
                    .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { (_) in
                        // Active 상태
                        print("did become active")
                    }
                    .onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { (_) in
                        // InActive 상태
                        print("will resign active")
                    }
                    .onReceive(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)) { (_) in
                        // Background 상태
                        print("did enter background")
                    }
                    .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { (_) in
                        // Background -> Foreground 상태
                        print("will enter foreground")
                    }
                    .onReceive(NotificationCenter.default.publisher(for: UIApplication.willTerminateNotification)) { (_) in
                        // 앱 종료 시
                        print("will terminate")
                    }
            }
        }
    }

     

    AppDelegate와 SceneDelegate 살리기

    • `AppDelegate` 클래스 작성
    class AppDelegate: UIResponder, UIApplicationDelegate {
        func application(
            _ application: UIApplication,
            willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
        ) -> Bool {
            // Not Running 상태
            // 앱 실행시 최초로 실행할 코드 (ex. firebase configure)
            print("Not Running, will finish launching")
            return true
        }
        
        func applicationDidFinishLaunching(_ application: UIApplication) {
            // Not Running 상태
            // 초기화 코드
            print("Not Running, did finish launching")
        }
        
        func applicationWillTerminate(_ application: UIApplication) {
            // Not Running 상태
            // 앱이 종료되기 직전에 호출
            print("Not Running, will terminate")
        }
    }
    • `SceneDelegate` 클래스 작성
    class SceneDelegate: UIResponder, UIWindowSceneDelegate {
        var window: UIWindow?
        
        func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
            guard let _ = (scene as? UIWindowScene) else { return }
        }
    
        func sceneDidDisconnect(_ scene: UIScene) {
            // Background 상태
            // Scene이 백그라운드로 갈때 iOS는 리소스를 확보하기 위헤 Scene을 삭제할 수 있음
            // 특정 Scene을 Foreground로 가져올때 다시 연결 가능
            print("Background, did disconnect")
        }
    
        func sceneDidBecomeActive(_ scene: UIScene) {
            // Active 상태
            // 앱이 실제로 사용되기 전에 마지막으로 준비할 코드
            print("Active, did become active")
        }
    
        func sceneWillResignActive(_ scene: UIScene) {
            // InActive 상태
            // 앱 스위처 모드, 전화 왔을때 (ex. 음악을 듣고 있는데 전화가 옴 -> 여기서 음악을 정지)
            print("InActive, will resign active")
        }
    
        func sceneWillEnterForeground(_ scene: UIScene) {
            // InActive 상태
            // Background나 Not Running에서 Foreground로 들어가기 직전에 호출
            print("Active, will enter foreground")
        }
    
        func sceneDidEnterBackground(_ scene: UIScene) {
            // Background 상태
            // Foreground에서 Background로 갈때 호출
            // user data저장 -> 앱이 종료(deallocated from RAM) 되는 시점을 예측할 수 없기 때문
            print("Background, did enter background")
        }
    }
    • `AppDelegate`에 `SceneDelegate` 연결
    class AppDelegate: UIResponder, UIApplicationDelegate {
        // ...
        
        func application(
            _ application: UIApplication,
            configurationForConnecting connectingSceneSession: UISceneSession,
            options: UIScene.ConnectionOptions
        ) -> UISceneConfiguration {
            let sceneConfig = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
            sceneConfig.delegateClass = SceneDelegate.self
            return sceneConfig
        }
    }
    • `@UIApplicationDelegateAdaptor` 프로퍼티 래퍼로 만든 `AppDelegate` 넣어줌
    struct AppDelegateApp: App {
        @UIApplicationDelegateAdaptor var appDelegate: AppDelegate
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }
    • Sample Test Project Link
     

    practiceSwiftUI/StudyLifecycle at main · jaehwi95/practiceSwiftUI

    Simple repository for practicing SwiftUI. Contribute to jaehwi95/practiceSwiftUI development by creating an account on GitHub.

    github.com

     

     

    practiceUIKitBasics/StudyNotificationCenter at main · jaehwi95/practiceUIKitBasics

    Simple Repository for Studying UIKit Basics. Contribute to jaehwi95/practiceUIKitBasics development by creating an account on GitHub.

    github.com

     

    Reference

     

    NotificationCenter | Apple Developer Documentation

    A notification dispatch mechanism that enables the broadcast of information to registered observers.

    developer.apple.com

     

    ScenePhase | Apple Developer Documentation

    An indication of a scene’s operational state.

    developer.apple.com

     

    UIApplicationDelegateAdaptor | Apple Developer Documentation

    A property wrapper type that you use to create a UIKit app delegate.

    developer.apple.com

     

    'Swift' 카테고리의 다른 글

    SwiftUI - Navigation  (1) 2024.07.23
    AppDelegate & SceneDelegate  (0) 2024.07.21
    SwiftUI - App Protocol  (0) 2024.07.21
    ARC (Automatic Reference Counting)  (0) 2024.07.20
    Swift - Memory 기초  (0) 2024.07.20