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")
    }
}
  • AppDelegateSceneDelegate 연결
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