Swift 프로젝트 생성에서 Life Cycle 선택하기

NB·2021년 1월 18일
4

Swift

목록 보기
2/4
post-thumbnail

서론

Xcode에서 iOS 개발 프로젝트를 실시하게 된다면, Life Cycle 을 선택하는 필드가 있다. 선택할 수 있는 선택지는 SwiftUI AppUIKit App Delegate 가 존재한다. 어떤 것을 선택해야 할까?

iOS 개발을 시작하면서 알게 된 것은 SwiftUI는 iOS 13 이상부터 지원을 한다고 한다. 그래서 개발을 해서 서비스를 해야 되는 입장에서 현재 글을 작성하는 2021년 1월 기준으로 iOS 13 이상 점유율이 어떤지 파악해봤다.


iOS 점유율 살펴보기

먼저 해당 점유율은 2020년 12월 15일에 App Store에 측정한 수치이다. 우리가 볼 점은 전체 iOS 기기 중에서 iOS 13 이상의 기종은 90%를 차지하고 있고, iPadOS에서는 82%의 기종이 전체 시장을 차지하고 있다. 물론 소수의 기종에 대한 호환성도 중요하지만, 앞으로의 진행 상황을 보게 된다면, iOS 13 이상의 기종에서만 돌아가는 SwiftUI에 대한 선택도 좋다고 생각한다.


( 이 부분은 어디까지나 개인의 생각일뿐이다! )


그럼 아래서부터는 swiftUI를 사용한다는 가정하에 Life Cycle에 대해서 어떤 것을 선택해야 할지 갈린다. 먼저 서론에서 언급했던 SwiftUI AppUIKit App Delegate의 차이점은 어떤 것이 있는지 살펴보자.


폴더구조 차이점

먼저 간단하게 각 생명주기의 프로젝트를 생성시켜서 폴더구조의 차이점을 살펴보겠다.

먼저 두 프로젝트를 살펴보면 가장 큰 차이는 프로젝트명.swift, AppDelegate.swift, SceneDelegate.swift, LaunchScreen.storyboard 파일의 유무이다. 먼저 LaunchScreen.storyboard 파일은 추후에 다룰 내용이지만, 앱이 제일 처음에 실행될 때, 나타나는(기본적으로 1~5초) 화면이다. 이 화면은 storyboard 파일이 없어도 swiftUI로도 구현이 충분히 가능하기 때문에 배제하여도 가능하다! 방법은 아래 영상을 참고하면 된다.

SwiftUI Splash Screen Using Xcode 12 - Youtube



Delegate란?

그럼 우리는 AppDelegate.swiftSceneDelegate.swift, 이 두 파일의 역할을 먼저 알아야 할 필요가 있다. 그 전에 먼저 iOS의 생명주기 변화를 한 번 살펴보자. 용어를 몰라도 괜찮다.

Delegate란 사전적 용어로 "대리인", "대표"라는 뜻을 가지고 있습니다!

  • iOS 12 이하

  • iOS 13 이상

용어에 대한 지식이 없어도 위 사진을 보게 된다면, iOS 12에서 13으로 넘어오면서 app delegate의 역할이 Process Lifecycle, UI Lifecycle에서 process Lifecycle, Session Lifecycle로 분리되었고, UI Lifecyclescene delegate로 빠진 것을 확인할 수 있다. 그럼 왜 이런 변화가 일어났던 것일까?

기존에는 하나의 앱에는 하나의 화면만 가능했다. 하지만 iOS 13부터는 아래 사진과 같이 하나의 앱에서 2개 이상의 화면을 분리하는 게 가능해졌다. 그렇기에 scene delegateUI Lifecycle을 맡아서 관리하고, app delegate가 내부에 있는 Session Lifecycle를 통해서 scene에 대한 모든 정보를 관리하게 된다. 물론 앱 자체의 생명주기도 포함이다.

그렇기 때문에, 우리는 프로젝트를 생성하면 AppDelegate.swiftSceneDelegate.swift와 같이 2개의 파일이 생성된 것을 확인할 수 있다. 물론 프로젝트가 iOS 13 미만을 지원하게 된다면, @available(iOS 13.0, *) 해당 구문이 추가된 메소드마다 위에 추가해주면 된다. 해당 구문은 iOS 13 이상부터 가능합니다! 라는 뜻이다.

# AppDelegate

먼저 AppDelegate는 클래스이며, 이 클래스를 통해서 app delegate라는 앱의 객체가 생성된다. app delegate는 앱의 상태에 따라서 응답하는 window를 만든다.

또한, AppDelegate 클래스 위를 보면 앱의 진입 주요 지점인 기존 Xcode11에서는 @UIApplicationMain, Xcode12에서는 @main이라고 데코레이터가 존재한다. 이 둘의 내부코드를 보면 결국 UIApplication 인스턴스를 만들고 app delegate를 호출하는 방식이다. 이 둘의 차이에 대해서는 여기서 더 자세하게 찾아볼 수 있다.

그럼 이제 파일에는 어떤 것이 적혀있는지 알아보자. 사실 기본적으로 너무 친절하게 주석으로 작성되어 있다. 그렇기에 큼직하게 어떤 것이 적혀있는지 살펴보자.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // 앱이 처음 실행된 뒤 실행
    return true
}

// MARK: UISceneSession Lifecycle

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
    // Scene이 새로 생긴 뒤 실행
}

func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
    // Scene이 삭제된 뒤 실행
}

// MARK: - Core Data stack
// 프로젝트를 생성할 때, core data를 추가하였기 때문에 생성된 코드입니다.

lazy var persistentContainer: NSPersistentContainer = {
    // 앱의 영구저장소를 만들고 반환
}

// MARK: - Core Data Saving support
// 프로젝트를 생성할 때, core data를 추가하였기 때문에 생성된 코드입니다.

func saveContext () {
    // core data 저장을 지원합니다.
    // 코드를 살펴보니 context에 변경이 일어나면 자동적으로 저장을 수행하는 코드입니다.
}

# SceneDelegate

iOS 13으로 발전하면서 기존의 window 개념이 scene 개념으로 대체되었다. 그렇기에 기존에 AppDelegate 파일에 window가 위치했던 것과 다르게, SceneDelegate 파일로 window가 위치하고 있다. 파일에 대해서 한 번 살펴보자.

/*
    window 변수가 존재한다. 만약 scene을 지원하지않는 os를 타겟으로 한다면,
    해당 변수를 AppDelegate.swift로 이동시켜야 한다.
    
*/
var window: UIWindow?


func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // scene이 앱에 추가될 때 호출
    // 여기서 ContentView()를 호출하면서, 영구저장소를 environment로 등록시켜준다.
}

func sceneDidDisconnect(_ scene: UIScene) {
    // scene이 연결이 해제될 때 호출 (다시 연결될 수 있음!)
}

func sceneDidBecomeActive(_ scene: UIScene) {
    // scene과의 상호작용이 시작될 때 호출
}

func sceneWillResignActive(_ scene: UIScene) {
    // scene과의 상호작용이 중지될 때 호출 (e.g. 다른 화면으로의 전환)
}

func sceneWillEnterForeground(_ scene: UIScene) {
    // scene이 foreground로 진입할 때 호출
}

func sceneDidEnterBackground(_ scene: UIScene) {
    // scene이 background로 진입할 때 호출
}


Delegate 파일이 없어요!

프로젝트 생성 시에 생명주기를 UIKit App Delegate가 아닌 SwiftUI App로 생성하게 된다면, 위에서 언급했던 폴더구조에서 AppDelegate.swiftSceneDelegate.swift 파일이 없다는 것을 눈치챘을 것이다. 그 대신 프로젝트명.swift가 존재한다. 해당 파일을 살펴보니 다음과 같다.

import SwiftUI

@main
struct Lifecycle_SwiftUIApp: App {
    let persistenceController = PersistenceController.shared

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}

AppDelegate.swift에 존재했던 @main이 위에 붙어있고, App을 채택한 구조체가 존재한다. 여기서 App 프로토콜은 main() method에 대한 Default 구현을 제공해준다. 그리고 앱의 body에서는 Scene 프로토콜을 따르는 인스턴스여야 한다. 그리고 기존에 SceneDelegate에서 담당했던 Life cycle을 scenePhaseWindowGroup에 onChange 이벤트를 통해서 감지할 수 있다.

import SwiftUI

@main
struct Lifecycle_SwiftUIApp: App {
    // scenePhase를 환경값에 등록시켜준다.
    @Environment(\.scenePhase) private var scenePhase
    
    let persistenceController = PersistenceController.shared

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
        .onChange(of: scenePhase) { (newScenePhase) in
            switch newScenePhase {
            case .active:
                AppManager.shared.isActive = true
                print("장면이 이제 활성화되었습니다!")
            case .inactive:
                print("장면이 이제 \(AppManager.shared.isActive ? "background" : "foreground")에 진입합니다.")
            case .background:
                AppManager.shared.isActive = false
                print("장면이 이제 배경에 있습니다!")
            @unknown default:
                print("의도치 않은 상태")
            }
        }
    }
}

/*
    Foreground -> Background 또는 Background -> Foreground 를 알아내기 위한 싱글톤 객체
*/
class AppManager {
    var isActive = false
    static var shared = AppManager()
    private init() {}
}

위 코드에서 AppManager을 이용해서 어떠한 Action을 제작해도 될 것이다. 이것이 SwiftUI App로 프로젝트를 생성하여 생명주기를 다루는 방법이다. 하지만 위에서 해당 파일에서 작성한 App, Scene, WindowGroup, scenePhase 전부 iOS 14 이상에서만 돌아간다는 사실이 존재한다.



결론 및 정리

사실상 현재 iOS 13의 점유율을 봐서는 SwiftUI를 사용해도 정상적으로 서비스를 운영할 수 있다는 생각이 들었다. 하지만 생명주기를 선택하는 과정에서 아직은 SwfitUI App를 선택하는 것은 이른 선택이 아닐까라고 생각한다. 아직까지 iOS 14의 시장 점유율은 60% ~ 70% 사이를 유지하고 있기때문이다. 언젠가는 iOS 14 점유율 또한 높아질 수 있겠지만, 글을 작성한 현재는 UIKit App Delegate를 선택해서 프로젝트를 생성하는 것이 맞다고 생각한다!

물론 기본적으로 iOS 13 이상만 지원한다는 가정이라, 추후 프로젝트의 진행 상황에 따라서 언제 바뀔지는 모르겠다 ㅎㅎ..



긴 글 읽어주셔서 감사합니다! 🙌



💡 중요

  • 프로젝트를 생성하면서 Life Cycle을 선택할 때, iOS 점유율 및 iPadOS 점유율에 따라서 선택하자!

  • SwiftUI App를 선택하면 AppDelegate.swiftSceneDelegate.swift 대신 프로젝트명.swift가 생성된다!

  • LaunchScreen.storyboard 없어도 Launch Screen을 만들 수 있다!

참고자료

  1. iOS Application Life Cycle - Jake
  2. SceneDelegate란 - 마리김
  3. Bye Bye AppDelegate! SwiftUI App Life Cycle - Sanzeev Gautam
  4. SwiftUI 팁 : SwiftUI 앱의 활성, 비활성 및 백그라운드 상태 감지 - ICHI.PRO
  5. [SwiftUI] Multi-platform App 만들기 - 프로젝트 세팅 - eungding
  6. @main - Github apple/swift-evolution
profile
𝙄 𝙖𝙢 𝙖 𝙛𝙧𝙤𝙣𝙩𝙚𝙣𝙙 𝙙𝙚𝙫𝙚𝙡𝙤𝙥𝙚𝙧 𝙬𝙝𝙤 𝙚𝙣𝙟𝙤𝙮𝙨 𝙙𝙚𝙫𝙚𝙡𝙤𝙥𝙢𝙚𝙣𝙩. 👋 💻

4개의 댓글

comment-user-thumbnail
2021년 3월 2일

감사합니다. 즐겨찾기하고 봐야겠네요

1개의 답글
comment-user-thumbnail
2021년 4월 1일

도움이 많이 되었습니다, 정말 감사합니다!!

1개의 답글