iOS/SwiftUI

[iOS/SwiftUI] SwiftUI 에서 LifeCycle 관리

yanni13 2024. 4. 5. 01:43

 

 

오늘은 iOS의 생명주기에 대해서 다뤄보겠다!

 

스터디에서 면접질문으로 가져온 질문은 아래와 같지만,

해당 질문들은 UIKit 기반의 LifeCycle이기 때문에 SwiftUI의 관점에서의 lifeCycle에 대해 글을 작성하고

UIKit LifeCycle과 비교하며 질문에 답을 해보는 식으로 글을 포스팅하겠다.

 

 

 

 

1️⃣ SwiftUI의 lifeCycle

 

SwiftUI에서 프로젝트를 만들고 (프로젝트명) App 파일에 들어가면 아래와 같은 코드를 볼 수 있다.

@main, Scene, App 등 개발할 때 자세하게 보지 않았던 코드들이 나열되어 있는데 이 파일은 SwiftUI에서의 LifeCycle에 관해 다루고 있다.

import SwiftUI

@main
struct LifeCycleApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

 

SwiftUI에서는 AppDelegate를 사용하는 대신 App 프로토콜에 의해 정의되며 이 프로토콜에서 앱의 생명주기 이벤트를 관리한다.

SwiftUI2.0 이후는 이런 이벤트들을 Scene나 View의 수명주기와 관련된 수식어를 사용하여 직접 처리할 수 있다.

 

 

2️⃣ 앱의 각 상태(Not Running, Inactive, Active, Background, Suspended)에서 할 수 있는 작업

 

UIKit

  • Not Running : 앱이 시작되지 않았거나 시스템에 의해 종료된 상태이다. 어떠한 코드도 실행되지 않음
  • Inactive : 앱이 실행 중이지만 이벤트를 받지 않는 상태이다. 일반적으로 상태 전환 중 잠시 머무는 상태 
    • ex) 사용자가 알림 센터를 열었을 때
  • Active : 앱을 실행하고 있고 이벤트를 받고 있을 때. 사용자와의 대화형 이벤트가 이루어지는 주 상태이다
  • Background : 앱이 백그라운드 상태로 전환되어 화면에 보이지 않는 상태. 
    • 이 상태에서는 짧은 시간 동안 백그라운드 작업을 수행할 수 있고, 이후 시스템에 의해 Suspended상태로 전환될 수 있다.
  • Suspended : 앱이 백그라운드에 있지만, 실행 중인 코드는 없고 메모리에 있지만 cpu 시간을 소모하지 않는 상태이다. 시스템이 메모리를 확보해야 할 때 suspended상태의 앱을 종료시킬 수 있다.

 

 

해당 이미지는 apple 공식 문서에서 설명하는 lifeCycle 사진을 가져왔다.

 

즉, Not Running 상태에서 시작되어 Foreground(Inactive, Active)를 통해 사용자와 상호작용하다가, 마지막으로 Suspended상태를 거쳐 다시 Not Running 상태가 된다.

 

 

SwiftUI

  • Active : 앱이 활성화 상태이며 사용자와 상호작용 할 수 있을 경우. 사용자의 입력을 받아 처리한다
  • Inactive : 앱이 활성화 상태는 아니지만 여전히 실행 중이고 이벤트를 받을 수 있다. 이 상태에서는 중요하지 않은 작업을 일시 중지하거나, 사용자 입력을 기다리는 상태이다.
  • background : 앱이 백그라운드에 있을 경우. 여전히 코드를 실행할 수 있지만 사용자 인터페이스에서는 보이지 않는다. 데이터 저장, 네트워크 요청 완료, 위치 업데이트와 같은 작업이 가능하며 실행시간은 제한적이다. -> 화면이 내려가거나 꺼진 상태를 의미

 

SwiftUI 앱에서 시스템이 백그라운드 작업을 모두 정지시키고 메모리를 회수하면 앱은 Suspended 상태에 들어가게 되지만, UIKit의 Suspended상태에 해당하는 것은 SwiftUI에 명시적으로 없기 때문에 어떠한 코드도 실행되지 않는다.

 

여기서 오해하면 안 되는 것은 background 상태와 suspended 상태가 동일한 게 아니라는 것이다!!

-> 인 줄 알았지만 background상태 안에 suspend 상태가 존재하는 것이었음 ㅎ

 

background는 일부 작업을 처리하고 제한적이지만 특정 이벤트에 응답할 수 있는 상태이지만, Suspended 상태는 앱이 비활성화된 상태이며 아무런 작업도 수행하지 않는 상태를 의미한다.

 

import SwiftUI

@main
struct LifeCycleApp: 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:
                        break
                    }
                }
        }
    }
}

 

swiftUI의 LifeCycle을 확인할 수 있는 간단한 예제 코드를 구현했고 위에 코드를 build 해서 실행시켜 보자.

 

 

그러면 앱이 실행 중임을 의미하는 active 상태가 찍히는 것을 확인할 수 있다.

 

시뮬레이터에서 해당 앱을 나가보자.

 

그러면 inactive, background까지 모두 출력되는 걸 확인할 수 있다.

 

 

not running, Foreground, Background는 iOS 앱의 lifecycle이므로 SwiftUI, UIkit 모두 공통으로 사용되는 듯하다.

 

 

3️⃣ 앱 상태 변화에 따라 호출되는 메서드들

애플에서 제공하는 life cycle events

 

  • onAppear : view가 화면에 나타날 때 호출된다. 데이터를 로드하거나 초기화하는 데 사용된다.
  • onDisappear : view가 화면에서 사라질 때 호출된다. view가 메모리에서 해제되기 전에 정리작업을 수행하는 데 사용된다.
  • onChange : 특정 상태 속성이 변경될 때 호출된다. 변경된 값에 따라 특정 동작을 수행하는데 사용된다.
  • ScenePhase : @Environment 속성 래퍼를 사용하여 앱의 현재 상태(Active, Inactive, Background)를 감지하고 행동을 정의할 수 있다.
@Environment(\.scenePhase) private var scenePhase

 

 

onAppear 예시

import SwiftUI

struct LoginView: View {

    @State private var isSplashShown = true
    
    var body: some View {
        NavigationAvailable {
            VStack {
                if isSplashShown {
                    SplashView()
                        .onAppear {
                            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                                withAnimation {
                                    isSplashShown = false
                                }
                            }
                        }
                } else {
                    NavigationLink(destination: NumberVerificationView()) {
                        Text("회원가입")
                    }
                    .padding()
                }
            }
        }
    }
}

 

위에 코드는 onAppear에 SplashView가 화면에 나타날 때 실행될 클로저를 정의하고 있다. 1초 동안 SplashView를 보여주도록 한다.

1초가 지나면 isSplashShown을 false로 변경해 NavigationLink 화면을 보여준다.

 

 

 

4️⃣ 백그라운드에서 작업을 완료하기 위한 방법

 

Background Tasks를 활용해 백그라운드 작업을 등록한다. 

Background Fetch : 주기적으로 앱의 새로운 콘텐츠를 가져와 업데이트할 수 있도록 하는 기능

Background App Refresh : 사용자가 앱을 다시 열었을 때 최신 정보를 제공할 수 있도록 백그라운드에서 앱 콘텐츠를 새로 고침 가능하도록 한다.

Push Notifications : 원격 알림을 사용하여 앱에 정보를 전송하고 필요한 경우 백그라운드 작업을 유발할 수 있다.

Location Updates : 백그라운드에서 위치 업데이트를 받을 수 있고, 앱의 plist파일에 백그라운드 모드로 위치 업데이트를 사용한다는 것을 명시해야 한다.

Background Music and Audio : 음악이나 오디오앱은 백그라운드에서 계속 실행될 수 있다.

Bluetooth Communication  : 블루투스 기기와의 통신을 유지하기 위해 백그라운드에서 사용 가능

 

앱이 백그라운드 상태로 들어서게 되면 추가 할당 시간을 받지 않는다고 한다.

그렇지만 노래를 틀어놓고 갑자기 끊기거나 하는 경우가 생기면,,, 안될 테니 어떤 조건에서 허용해 주는지도 한번 찾아보았다.

 

 

특정 조건에서 허용하려면 프로젝트 info에서 설정을 바꿔야 한다고 한다.

 

 

느낀 점

웹 프론트를 공부했을 때는 lifecycle을 신경 쓰지 않고 개발하여도 문제 되는 게 없고 크게 신경 쓸게 없었는데 ios 개발을 하면서 ios lifecycle, view lifecycle, swiftUI lifecycle, UIKit lifecycle.... 등등 알아야 할 게 너무 많았고 실제 앱 출시할 때는 불필요한 리소스들을 줄이기 위해 라이프사이클을 다 따져가며 개발해야 하니 더 열심히 공부해야겠다고 느꼈다.