진입점(Entry point) 부터 앱 화면이 보이기 직전의 상태까지의 과정에 대해 알아보자.
Entry Point
위 사진에는 표기되지 않았지만 iOS 15이후로는 앱 시작시 Prewarming이라는 과정을 진행할 수 있다고 한다. 지금은 main 함수 호출부터 알아보자.
진입점으로 제일 처음에 실행되는 코드이다.
// main.m
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
Objective-C로 생성된 프로젝트에서는 main.m 파일이 생성되어 위와 같은 내용의 main 함수를 확인 가능하다고 한다.
하지만 Swift 프로젝트에서는 main 함수를 확인할 수 없는데 @UIApplicationMain 또는 @main을 클래스에 작성하면, 시스템이 자동으로 main 함수를 호출한다
@UIApplicationMain // Swift 5.3 이전
class AppDelegate: UIResponder, UIApplicationDelegate {
}
@main // Swift 5.3 이후
class AppDelegate: UIResponder, UIApplicationDelegate {
}
@main // SwiftUI 프로젝트
struct myApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
@main 를 사용하여 긴 코드가 깔끔하게 한줄로 동일하게 처리 가능하다!
정말 동일하게 처리될까?
Swift 프로젝트를 생성하여 main.swift 라는 파일을 만든다음 아래와 같이 UIApplicationMain 함수를 작성 해보자. 해당 함수를 감싸는 코드 블럭을 작성하지 않는다.(top-level)
// main.swift
// 함수인데 코드 블럭 내부에서 작성되지 않는다..
UIApplicationMain(
CommandLine.argc,
CommandLine.unsafeArgv,
nil,
NSStringFromClass(AppDelegate.self)
)
작성 이후 빌드를 해보면, 아래와 같이 두가지 에러가 발생한다.
1. top-level 코드가 포함된 모듈에서는 @main을 사용할 수 없다
즉, main.swift 파일 내부에 top-level코드가 이미 모듈에 포함되었기 때문에 @main은 사용하지 못한다.
top-level 코드란?
최상위 수준 선언(top-level declarations):
최상위 수준에서 선언되어 동일한 모듈에 속한 모든 코드에 접근이 가능하다.
(개인적으로 자주 사용하는 log 라이브러리 SwiftyBeaver의 log 상수 선언와 같은..)
실행 가능한 최상위 수준 코드(executable top-level code):
선언 뿐만 아니라 구문과 표현식을 포함하고 있으며, 프로그램에 대해 top-level entry point로만 허용된다.
- main.swift 파일의 UIApplicationMain과 같은 코드 블럭에 포함되어있지 않은 코드
- @UIApplication, @main attribute 등
2. 심볼의 중복
duplicate symbol for architecture로 검색해보니 동일한 함수가 중복 로드되었을 때 발생 한다고 한다.
결론적으로 UIApplicationMain의 호출과 @main을 특정 클래스에 작성하는것은 동일하다!
main 함수가 실행되면 UIApplicationMain 함수가 실행된다.
func UIApplicationMain(
// argv의 갯수.
_ argc: Int32,
// 인자의 변수 목록
_ argv: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>,
// UIApllication 클래스 또는 하위 클래스 이름, nil을 지정하면 UIApplication으로 가정
_ principalClassName: String?,
// application delegate가 인스턴스화 되는 클래스 이름
_ delegateClassName: String?
) -> Int32
함수가 실행 되면 각 인자의 내용과 같이 UIApplication 및 UIApplicationDelegate 프로토콜을 채택한 클래스의 인스턴스가 생성된다.
info.plist에 설정된 기본 스토리보드 파일이 로드 된다. info.plist에 지정되어 있지 않다면 이 과정은 스킵된다.
https://developer.apple.com/documentation/xcode-release-notes/xcode-13-release-notes
앱을 초기화 하고 실행을 위한 준비를 진행한다. 기본 storyboard 또는 nib 파일을 로드된 이후 App State Restoration 진행되기 이전에 application(_:willFinishLaunchingWithOptions:) 메서드가 호출 된다.
해당 메서드가 호출될 때 앱은 inactive state 상태이다.
optional func application(
_ application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool
https://developer.apple.com/documentation/uikit/uiscenedelegate/restoring_your_app_s_state
앱 초기화를 완료하고 마지막 수정을 진행한다. App State Restoration이 발생한 이후에 호출되지만 앱의 화면 및 다른 UI가 표시되기 이전에 application(_:didFinishLaunchingWithOptions:) 메서드가 호출된다.
optional func application(
_ application: UIApplication, //
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool
이 메서드가 반환된 후 어느 시점에 시스템이 App delegate의 다른 메서드를 호출하여 앱을 active(foreground) 상태 또는 background 상태로 이동.
아래 플로우에서 Entry Point 부분만 살펴 보았다.
Entry Point 및 App LifeCycle에 대한 순서도와 같은 내용을 애플 Documentation Archive에서 찾지 못했고 다른분들의 정리글에서 출처로 표기된 예전 주소로 들어가면 UIKit 애플 최신문서로 진입되어 볼수가 없어서 답답 했지만 참고자료가 있어서 도움이 되었다.
[포스팅 예정목록]
출처 및 참고