bounds와 frame은 UIView의 프로퍼티이다.
두 프로퍼티는 다른 좌표계를 기준으로 뷰의 위치와 크기를 표현한다. 이 차이를 이해하는 것은 뷰의 레이아웃 및 변환을 제어할 때 중요하다.
실제 디바이스 없이 오로지 시뮬레이터만을 사용하여 개발할 때 가능한 것과 제한되는 것이 존재한다.
iOS에서 앱 화면의 콘텐츠를 표시하는 로직과 관리를 담당하는 객체는 ViewController이다.
UIViewController 클래스는 앱의 사용자 인터페이스의 한 화면을 관리한다. 화면의 콘텐츠는 View와 View와 관련된 여러 서브뷰들로 구성되며, ViewController는 이러한 뷰들의 레이아웃, 사용자와의 상호작용, 화면 전환, 데이터 표시 및 업데이트 등과 관련된 로직을 처리한다.
또한, UIViewController는 생명 주기 메서드(lifecycle methods)를 통해 뷰의 로딩, 화면에 표시될 때, 화면에서 사라질 때 등의 이벤트에 대한 처리를 할 수 있다. 이러한 생명 주기 메서드들은 viewDidLoad(), viewWillAppear(_:), viewDidAppear(_:), viewWillDisappear(_:) 등이 있다.
SwiftUI에서는 UIKit의 UIViewController에 해당하는 직접적인 개념이 없다. 대신, SwiftUI는 View를 중심으로 설계되었다.
그리고 데이터의 변화를 감지하고 자동으로 UI를 업데이트하는 데이터 바인딩 기능이 중요하게 쓰인다. @State, @Binding, @ObservedObject, @EnvironmentObject 등의 프로퍼티 래퍼를 사용하여 뷰와 데이터 간의 연결을 설정한다.
따라서, SwiftUI에서는 ViewController의 관리 및 로직 처리가 필요 없이 직접적으로 View와 관련된 선언적인 코드를 작성함으로써 UI와 로직을 구성한다. 그러나 UIViewController와 SwiftUI의 View를 함께 사용해야 하는 경우도 있다.
App thinning은 iOS 앱의 크기를 최적화하여 사용자의 장치에 필요한 리소스만 다운로드하게 되는 기능이다. 이로 인해 앱의 다운로드와 설치 시간이 줄어들며, 사용자의 장치에서 더 적은 저장 공간을 차지하게 된다. App thinning에는 크게 세 가지 주요 기술이 포함된다.
Bitcode
: Bitcode는 앱의 컴파일 된 코드의 중간 표현이다. 앱을 App Store에 업로드 할 때, 최종 바이너리를 생성하기 전에 bitcode 형식으로 제출된다. 이후 Apple이 새로운 최적화를 도입하면, 이 bitcode를 기반으로 앱을 다시 컴파일하여 사용자에게 제공한다. 이렇게하면 새로운 최적화를 수용하기 위해 앱을 직접 다시 컴파일하고 업로드 할 필요가 없다.
Slicing
: Slicing 기술은 앱의 바이너리를 서로 다른 장치 및 운영 체제 버전에 적합한 여러 버전으로 분할한다. 예를 들어, 더 오래된 iPhone 모델과 최신 iPad 모델에는 다른 그래픽 리소스 및 아키텍처 요구 사항이 있을 수 있다. App Store는 사용자의 특정 장치에 가장 적합한 앱 슬라이스를 자동으로 제공하여 불필요한 리소스를 다운로드 받지 않게 한다.
On-Demand Resources (ODR)
: ODR는 앱이 처음 실행될 때 필요하지 않은 리소스를 연기하여 다운로드하는 기능이다. 예를 들어, 게임에서 여러 레벨이 있을 경우 첫 번째 레벨의 리소스만 초기에 다운로드하고, 사용자가 다음 레벨로 이동할 준비가 되면 해당 리소스를 다운로드 받을 수 있다.
UIKit 및 SwiftUI에서 앱이 시작될 때 main.c의 UIApplicationMain 함수에 의해 생성되는 주요 객체는 UIApplication 인스턴스이다.
UIApplicationMain 함수는 앱의 AppDelegate를 생성한다. AppDelegate는 앱의 수명 주기에 따라 발생하는 여러 이벤트를 처리하는 역할을 한다.
UIKit에서는 AppDelegate가 UIApplicationDelegate 프로토콜을 준수하며, 여기에는 앱의 시작, 종료, 백그라운드 진입 등의 이벤트에 대한 콜백 메서드가 포함되어 있다.
SwiftUI에서는 @main 속성을 사용하여 앱의 진입점을 지정할 수 있다. SwiftUI 앱의 진입점은 주로 App 프로토콜을 준수하는 구조체에 위치한다. 이 구조체 내에서 앱의 수명 주기 이벤트를 처리할 수 있다. 그러나 SwiftUI 앱도 내부적으로 UIApplication 인스턴스와 함께 작동하므로, 필요에 따라 UIApplicationDelegate와 같은 전통적인 UIKit 콜백에 액세스할 수 있다.
@main 어노테이션은 어떤 타입이 앱의 시작점으로 사용될 것인지를 지정한다. @main 어노테이션의 도입으로, main.swift 파일을 포함하지 않는 Swift 프로젝트를 생성할 수 있게 되었다.
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// ...
}
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
앱의 상태는 크게 active
, inactive
, background
, suspended
로 구분된다. 여기서 active
와 inactive
는 앱이 foreground
상태에 있을 때의 상태이며, background
와 suspended
는 앱이 background
상태에 있을 때의 상태이다.
UIKit에서는 주로 AppDelegate의 메서드를 통해 앱의 상태 변화를 감지하고 대응한다.
SwiftUI 2.0부터는 @Main 속성과 함께 App 프로토콜을 채택하는 구조로 변경되었고, 이를 통해 앱의 생명주기를 관리한다. SwiftUI에서는 ScenePhase라는 환경 변수를 사용하여 앱의 상태 변화를 감지하고 대응한다.
struct ContentView: View {
@Environment(\.scenePhase) var scenePhase
var body: some View {
Text("Hello, World!")
.onChange(of: scenePhase) { newPhase in
switch newPhase {
case .active:
print("App is active")
case .inactive:
print("App is inactive")
case .background:
print("App is in the background")
@unknown default:
print("Unknown state")
}
}
}
}
UIKit은 UIApplicationDelegate 프로토콜을 통해 앱의 생명주기를 핸들링한다.
SwiftUI에서는 기존 AppDelegate의 역할을 App 및 Scene의 조합으로 나누게 된다. 이렇게 변경된 생명주기로 인해 몇몇 AppDelegate 메서드들은 SwiftUI에서 다르게 대응된다.
body: SwiftUI의 앱 진입점이다. 여기에서 앱이 어떤 화면(scene)으로 시작할지 정의한다.
ScenePhase 환경 변수: SwiftUI에서는 앱의 생명주기 상태를 감지하기 위해 ScenePhase 환경 변수를 사용한다.
.active: 앱이 활성 상태일 때.
.inactive: 앱이 비활성 상태일 때.
.background: 앱이 백그라운드 상태일 때.
이외에, 기존 AppDelegate에서 수행하던 초기화, 외부 리소스 접근, 알림 관리 등의 기능을 SwiftUI에서 수행하려면 UIApplicationDelegateAdaptor와 UIApplicationDelegate를 함께 사용해야 한다.
예를 들어, SwiftUI 앱에서 UIApplicationDelegate의 didFinishLaunchingWithOptions와 같은 메서드를 사용하려면, 다음과 같이 코드를 작성할 수 있다.
@main
struct App: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// 여기에서 초기화 코드 작성
return true
}
}
전화 수신: 사용자가 앱을 사용 중일 때 전화가 오면 앱은 In-Active 상태로 전환된다. 전화 통화 화면이 나타나기 전의 짧은 시간 동안 앱은 여전히 전경에 있지만 사용자와의 상호작용은 일시 중단된다. 전화 통화 화면이 표시되면 앱은 백그라운드 상태로 전환된다.
멀티태스킹 뷰: 사용자가 홈 버튼을 두 번 눌러 멀티태스킹 뷰를 활성화하면 앱은 In-Active 상태로 전환된다. 다른 앱을 선택하거나 홈 화면으로 돌아가면 현재 앱은 백그라운드 상태로 전환된다.
로컬 알림 또는 푸시 알림: 앱을 사용하는 중에 알림이 도착하면 알림 배너가 상단에서 나타나며, 이 동안 앱은 In-Active 상태가 된다. 사용자가 알림을 터치하여 해당 알림과 관련된 다른 액션을 취하면 앱은 백그라운드로 전환될 수 있다.
Control Center 및 Notification Center: 사용자가 Control Center나 Notification Center를 스와이프하여 활성화하면, 앱은 In-Active 상태로 전환된다.
스크린샷: 사용자가 스크린샷을 캡처하는 동안 앱은 잠시동안 In-Active 상태가 된다.
데이터 저장: 앱이 In-Active나 Background 상태로 전환될 때, 진행 중인 작업이나 유저 데이터를 안전하게 저장한다. 이는 앱이 갑자기 종료되더라도 데이터 손실을 방지할 수 있다.
네트워크 작업 일시 중지: 앱이 Background 상태로 전환될 때 활성화된 네트워크 작업을 일시 중지하거나 완료할 수 있다.
백그라운드 작업 시작: 앱이 백그라운드에서 계속 작업을 수행해야 하는 경우 (예: 위치 추적, 파일 다운로드), 백그라운드 작업을 시작할 수 있다.
리소스 해제: 메모리 제한이나 다른 리소스 제한을 고려하여 앱이 Background 상태로 전환될 때 불필요한 리소스를 해제한다.
UI 업데이트 일시 중지: 앱이 Background 상태로 전환될 때는 UI 업데이트가 필요하지 않으므로, 이를 일시 중지할 수 있다.
알림 관리: 백그라운드에서 일어난 이벤트에 따라 사용자에게 로컬 알림을 보낼 수 있다.
상태 복원: 앱이 다시 전경으로 전환될 때 이전 상태로 복원될 수 있도록 관련 데이터나 상태를 저장한다.
보안: 보안이 중요한 앱의 경우, 앱이 In-Active나 Background 상태로 전환될 때 화면을 잠글 수 있다.
타이머나 스케쥴러 관리: 활성화된 타이머나 스케쥴러가 있다면 앱의 상태 변화에 따라 이를 일시 중지하거나 재개할 수 있다.
오디오 세션 관리: 오디오 플레이백이나 녹음 기능이 있는 앱의 경우, 앱의 상태 변화에 따라 오디오 세션을 관리한다.