[iOS] TIL

Zoe·2023년 9월 10일
0

iOS

목록 보기
19/39

Frame, Bounds

bounds와 frame은 UIView의 프로퍼티이다.
두 프로퍼티는 다른 좌표계를 기준으로 뷰의 위치와 크기를 표현한다. 이 차이를 이해하는 것은 뷰의 레이아웃 및 변환을 제어할 때 중요하다.

frame

  • CGRect, 즉 origin과 size를 갖는다.
  • frame은 슈퍼뷰의 좌표계를 기준으로 뷰의 위치와 크기를 나타낸다.
  • 예를 들어, 슈퍼뷰 내에서 (x, y) 위치에 크기 (width, height)인 뷰가 있다면, 해당 뷰의 frame은 (x, y, width, height)로 표현된다.

bounds

  • CGRect, 즉 origin과 size를 갖는다.
  • bounds는 뷰 자체의 좌표계를 기준으로 해당 뷰의 크기와 위치를 나타낸다. 자신만의 좌표 기준을 갖는다는 의미이다.
  • 일반적으로 뷰의 bounds의 위치 (origin)는 (0, 0)이며, 크기는 해당 뷰의 실제 크기와 동일하다. 그러나 스크롤 뷰나 회전 및 스케일 변환을 적용한 뷰에서는 다를 수 있다.

SwiftUI에서의 frame, bounds

  • SwiftUI는 UIKit의 직접적인 후속작이 아니기 때문에, UIKit에서 익숙한 frame과 bounds의 개념이 그대로 적용되지는 않는다. 하지만 SwiftUI에는 뷰의 크기와 위치를 조절하는데 사용되는 유사한 메커니즘이 있다. 바로 frame이라는 메서드를 사용하여 뷰의 크기와 위치를 지정할 수 있다. 이 frame 메서드는 UIKit의 frame 개념과 유사하지만 완전히 같지는 않다.
  • 그러나, bounds와 같은 직접적인 개념은 SwiftUI에서는 존재하지 않는다. SwiftUI는 선언적인 방식을 사용하기 때문에 뷰의 레이아웃과 관련된 상세한 조절보다는 뷰의 구조와 동작을 중심으로 설계되었다. 따라서 UIKit과는 다른 방식으로 접근해야 한다. SwiftUI에서는 뷰의 크기나 위치를 명시적으로 지정하기보다는 뷰의 관계와 레이아웃 규칙을 정의하고, 시스템이 이를 기반으로 레이아웃을 계산하게 된다.
    참고 : https://zeddios.tistory.com/203

Simulator

실제 디바이스 없이 오로지 시뮬레이터만을 사용하여 개발할 때 가능한 것과 제한되는 것이 존재한다.

할 수 있는 것

  • UI 테스팅
  • 기능 테스팅
  • 디버깅
  • 프로파일링
  • OS 버전 테스팅

할 수 없는 것

  • 하드웨어 관련 테스팅: 카메라, 가속도계, 진동 등의 실제 하드웨어 기능은 시뮬레이터에서 테스트할 수 없다.
  • 푸시 알림: 시뮬레이터에서는 애플의 푸시 알림 서비스(APNS)를 통한 푸시 알림을 테스트할 수 없다.
  • 실제 성능 테스팅: 시뮬레이터는 Mac에서 실행되기 때문에 실제 디바이스의 성능과는 다를 수 있다. 따라서, 실제 성능 최적화와 관련된 테스팅은 디바이스에서 진행해야 한다.
  • 앱 스토어 배포: 실제 디바이스에서의 테스팅 없이 앱 스토어에 배포하는 것은 권장되지 않는다.
  • 인앱 결제: 시뮬레이터에서는 인앱 결제 테스트를 진행할 수 없다.
  • 특정 API 제한: 일부 API는 실제 디바이스에서만 작동하며 시뮬레이터에서는 작동하지 않을 수 있다.

iOS 내부 데이터 저장

  • UserDefaults: 소량의 사용자 설정이나 데이터를 저장하기 위한 인터페이스이다. 키-값 쌍으로 데이터를 저장하며, 사용하기 간편하지만 대용량 데이터나 복잡한 구조의 데이터를 저장하기에는 적합하지 않다.
  • Core Data: iOS에서 제공하는 객체 관계형 매핑 프레임워크로, 복잡한 데이터 구조와 대용량의 데이터를 저장하고 관리하기 위해 사용된다. SQLite와 같은 데이터베이스를 백엔드로 사용하지만, 객체 지향적인 접근 방식을 제공한다.
  • FileManager (또는 NSFileManager): 파일 시스템에 직접 접근하여 파일 및 디렉터리를 생성, 읽기, 쓰기, 삭제하는 등의 작업을 할 수 있다.
  • Keychain: 사용자의 비밀번호, 인증서, 키와 같은 민감한 정보를 안전하게 저장하기 위한 시스템이다.

앱 화면의 콘텐츠를 표시하는 로직과 관리를 담당하는 객체

iOS에서 앱 화면의 콘텐츠를 표시하는 로직과 관리를 담당하는 객체는 ViewController이다.
UIViewController 클래스는 앱의 사용자 인터페이스의 한 화면을 관리한다. 화면의 콘텐츠는 View와 View와 관련된 여러 서브뷰들로 구성되며, ViewController는 이러한 뷰들의 레이아웃, 사용자와의 상호작용, 화면 전환, 데이터 표시 및 업데이트 등과 관련된 로직을 처리한다.
또한, UIViewController는 생명 주기 메서드(lifecycle methods)를 통해 뷰의 로딩, 화면에 표시될 때, 화면에서 사라질 때 등의 이벤트에 대한 처리를 할 수 있다. 이러한 생명 주기 메서드들은 viewDidLoad(), viewWillAppear(_:), viewDidAppear(_:), viewWillDisappear(_:) 등이 있다.

그렇다면 SwiftUI에서는?

SwiftUI에서는 UIKit의 UIViewController에 해당하는 직접적인 개념이 없다. 대신, SwiftUI는 View를 중심으로 설계되었다.
그리고 데이터의 변화를 감지하고 자동으로 UI를 업데이트하는 데이터 바인딩 기능이 중요하게 쓰인다. @State, @Binding, @ObservedObject, @EnvironmentObject 등의 프로퍼티 래퍼를 사용하여 뷰와 데이터 간의 연결을 설정한다.
따라서, SwiftUI에서는 ViewController의 관리 및 로직 처리가 필요 없이 직접적으로 View와 관련된 선언적인 코드를 작성함으로써 UI와 로직을 구성한다. 그러나 UIViewController와 SwiftUI의 View를 함께 사용해야 하는 경우도 있다.

App Thinning

App thinning은 iOS 앱의 크기를 최적화하여 사용자의 장치에 필요한 리소스만 다운로드하게 되는 기능이다. 이로 인해 앱의 다운로드와 설치 시간이 줄어들며, 사용자의 장치에서 더 적은 저장 공간을 차지하게 된다. App thinning에는 크게 세 가지 주요 기술이 포함된다.

Bitcode: Bitcode는 앱의 컴파일 된 코드의 중간 표현이다. 앱을 App Store에 업로드 할 때, 최종 바이너리를 생성하기 전에 bitcode 형식으로 제출된다. 이후 Apple이 새로운 최적화를 도입하면, 이 bitcode를 기반으로 앱을 다시 컴파일하여 사용자에게 제공한다. 이렇게하면 새로운 최적화를 수용하기 위해 앱을 직접 다시 컴파일하고 업로드 할 필요가 없다.

Slicing: Slicing 기술은 앱의 바이너리를 서로 다른 장치 및 운영 체제 버전에 적합한 여러 버전으로 분할한다. 예를 들어, 더 오래된 iPhone 모델과 최신 iPad 모델에는 다른 그래픽 리소스 및 아키텍처 요구 사항이 있을 수 있다. App Store는 사용자의 특정 장치에 가장 적합한 앱 슬라이스를 자동으로 제공하여 불필요한 리소스를 다운로드 받지 않게 한다.

  • 기기별 리소스 분리: 앱에는 여러 해상도의 이미지 (e.g. @1x, @2x, @3x), 다양한 아키텍처에 대한 바이너리 (e.g. armv7, arm64), 그리고 다양한 장치 및 화면 크기에 최적화된 기타 리소스들이 포함될 수 있다.
  • App Store는 사용자의 장치 및 iOS 버전에 따라 가장 적합한 슬라이스를 자동으로 생성한다. 예를 들면, iPhone 8 사용자에게는 @2x 이미지와 arm64 아키텍처용 바이너리가, iPad Pro 사용자에게는 @2x 또는 @3x 이미지와 arm64 아키텍처용 바이너리가 제공된다.
  • 앱의 Asset Catalog를 사용하여 다양한 해상도의 이미지와 기타 리소스를 관리한다. Xcode와 App Store가 이 정보를 활용하여 필요한 리소스만을 포함한 앱 슬라이스를 자동으로 생성한다.

On-Demand Resources (ODR): ODR는 앱이 처음 실행될 때 필요하지 않은 리소스를 연기하여 다운로드하는 기능이다. 예를 들어, 게임에서 여러 레벨이 있을 경우 첫 번째 레벨의 리소스만 초기에 다운로드하고, 사용자가 다음 레벨로 이동할 준비가 되면 해당 리소스를 다운로드 받을 수 있다.

  • ODR는 앱의 특정 부분이나 기능에 필요한 리소스를 연기하여 다운로드하는 방식이다.
  • 어떤 리소스가 초기 다운로드에 포함되어야 하며 어떤 리소스가 연기되어야 할지를 지정할 수 있다. 각 리소스 또는 리소스 그룹에는 태그를 지정하여 관리한다.
  • 사용자가 앱에서 특정 기능이나 콘텐츠에 접근할 때 필요한 리소스가 아직 다운로드되지 않았다면, 앱은 그 리소스를 On-Demand로 다운로드한다.

main.c의 UIApplicationMain

UIKit 및 SwiftUI에서 앱이 시작될 때 main.c의 UIApplicationMain 함수에 의해 생성되는 주요 객체는 UIApplication 인스턴스이다.
UIApplicationMain 함수는 앱의 AppDelegate를 생성한다. AppDelegate는 앱의 수명 주기에 따라 발생하는 여러 이벤트를 처리하는 역할을 한다.
UIKit에서는 AppDelegate가 UIApplicationDelegate 프로토콜을 준수하며, 여기에는 앱의 시작, 종료, 백그라운드 진입 등의 이벤트에 대한 콜백 메서드가 포함되어 있다.
SwiftUI에서는 @main 속성을 사용하여 앱의 진입점을 지정할 수 있다. SwiftUI 앱의 진입점은 주로 App 프로토콜을 준수하는 구조체에 위치한다. 이 구조체 내에서 앱의 수명 주기 이벤트를 처리할 수 있다. 그러나 SwiftUI 앱도 내부적으로 UIApplication 인스턴스와 함께 작동하므로, 필요에 따라 UIApplicationDelegate와 같은 전통적인 UIKit 콜백에 액세스할 수 있다.

UIKit

  • UIApplicationMain 함수: 전통적인 iOS 앱 개발에서는 main.c 또는 main.swift 파일에서 UIApplicationMain 함수를 호출하여 앱을 시작한다.
  • 생성되는 객체: UIApplicationMain 함수는 UIApplication 객체를 생성한다. 이 객체는 앱의 주요 이벤트 루프를 관리하고 주요 동작을 조정한다.
  • AppDelegate: UIApplicationMain 함수는 또한 앱의 AppDelegate를 초기화한다. 이 AppDelegate는 앱의 수명 주기 이벤트를 처리하며, UIApplicationDelegate 프로토콜을 준수한다.

SwiftUI

  • @main 어노테이션: SwiftUI는 @main 어노테이션을 사용하여 앱의 시작점을 지정한다. 이는 main.swift 파일과 UIApplicationMain 함수 호출을 대체한다.
  • 생성되는 객체: SwiftUI 앱도 내부적으로 UIApplication 인스턴스를 사용한다. 그러나 개발자가 직접 이 객체에 접근하거나 관리할 필요는 없다.
  • App 프로토콜: @main 어노테이션은 주로 App 프로토콜을 준수하는 구조체와 연관되어 있다. 이 구조체는 앱의 본문과 수명 주기 이벤트를 정의한다.

공통점

  • 둘 다 UIApplication 인스턴스를 사용하여 앱의 주요 이벤트 루프와 동작을 관리한다.
  • 앱의 수명 주기 이벤트를 처리하는 메커니즘이 있다 (UIKit의 AppDelegate와 SwiftUI의 App 프로토콜).

차이점

  • 시작점의 호출 방식과 위치가 다르다 (UIApplicationMain vs. @main 어노테이션).
  • SwiftUI는 앱의 수명 주기와 관련된 코드를 더 선언적으로 작성할 수 있다.
  • UIKit에서는 AppDelegate와 함께 여러 UIKit 수명 주기 메서드를 사용하며, SwiftUI에서는 App 프로토콜의 새로운 메서드를 사용한다.

@Main

@main 어노테이션은 어떤 타입이 앱의 시작점으로 사용될 것인지를 지정한다. @main 어노테이션의 도입으로, main.swift 파일을 포함하지 않는 Swift 프로젝트를 생성할 수 있게 되었다.

UIKit에서의 @main

  • 사용: UIKit을 사용하는 앱에서 @main은 주로 앱의 수명 주기 이벤트를 관리하는 AppDelegate 클래스와 함께 사용된다.
  • 구조: @UIApplicationMain 어노테이션의 대체제로 사용될 수 있다. 이전에는 @UIApplicationMain을 사용하여 AppDelegate를 앱의 시작점으로 지정했다.
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    // ...
}

SwiftUI에서의 @main

  • 사용: SwiftUI에서 @main은 주로 App 프로토콜을 준수하는 구조체와 함께 사용된다.
  • 구조: SwiftUI 앱의 시작점은 App 프로토콜을 준수하는 구조체로, 앱의 본문과 수명 주기 이벤트를 정의한다.
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

foreground, background

앱의 상태는 크게 active, inactive, background, suspended로 구분된다. 여기서 activeinactive는 앱이 foreground 상태에 있을 때의 상태이며, backgroundsuspended는 앱이 background 상태에 있을 때의 상태이다.

Foreground (active 및 inactive 상태)

  • Active 상태: 앱이 활성 상태이며 사용자와의 상호작용이 발생한다. 이 상태에서는 모든 기능과 리소스에 접근할 수 있다.
  • Inactive 상태: 앱은 여전히 foreground에 있지만 일시적으로 사용자와의 상호작용이 발생하지 않는다. 예를 들어, 전화나 SMS가 도착하면 앱이 잠시 inactive 상태로 전환될 수 있다.

Background (background 및 suspended 상태)

  • Background 상태: 앱은 백그라운드에서 작동 중이지만 코드가 실행될 수 있다. 그러나 이 상태에서도 제약 사항이 존재한다.
  • 제한된 실행 시간: 일반적으로 앱은 백그라운드로 전환된 후에 짧은 시간 동안만 실행된다. 추가 시간이 필요한 경우 배경 작업으로 등록해야 한다.
  • 네트워크 연결: 백그라운드에서의 네트워크 연결은 제한될 수 있다. 예를 들어, 백그라운드 데이터 전송을 위해 URLSession의 백그라운드 태스크를 사용해야 한다.
  • 리소스 제한: GPS, 카메라 등 일부 하드웨어 리소스에 대한 접근은 제한될 수 있다.
  • Suspended 상태: 앱은 메모리에 남아 있지만 어떤 코드도 실행되지 않는다. 시스템은 메모리를 회수하기 위해 언제든지 suspended 상태의 앱을 종료할 수 있다.
  • 음악 스트리밍 앱을 생각해보면, 사용자가 다른 앱을 사용하면서도 음악을 계속 듣기를 원할 것이다. 이 경우, 해당 음악 스트리밍 앱은 백그라운드에서 오디오 재생 기능을 사용하여 음악을 계속 재생할 수 있어야 한다.
  • 위치 추적 앱의 경우, 앱이 백그라운드 상태에 있을 때도 사용자의 위치를 계속 추적해야 할 수 있다. 이를 위해 백그라운드 위치 업데이트를 활성화해야 한다.

UIKit에서의 핸들링

UIKit에서는 주로 AppDelegate의 메서드를 통해 앱의 상태 변화를 감지하고 대응한다.

Foreground 상태 변화

  • 앱이 foreground로 진입할 때: applicationWillEnterForeground(_:)
  • 앱이 active 상태가 될 때: applicationDidBecomeActive(_:)

Background 상태 변화

  • 앱이 background 상태가 될 때: applicationDidEnterBackground(_:)
  • 앱이 종료될 예정일 때: applicationWillTerminate(_:)

SwiftUI에서의 핸들링

SwiftUI 2.0부터는 @Main 속성과 함께 App 프로토콜을 채택하는 구조로 변경되었고, 이를 통해 앱의 생명주기를 관리한다. SwiftUI에서는 ScenePhase라는 환경 변수를 사용하여 앱의 상태 변화를 감지하고 대응한다.

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")
                }
            }
    }
}

AppDelegate

UIKit의 AppDelegate 메서드들

UIKit은 UIApplicationDelegate 프로토콜을 통해 앱의 생명주기를 핸들링한다.

application(_:willFinishLaunchingWithOptions:)

  • 앱이 처음 시작될 때 호출된다.
  • 앱 초기화 과정에서 필요한 설정이나 데이터 로딩 등을 이곳에서 수행한다.
  • 예시: 앱 설정 또는 디바이스 환경 체크 등 초기 준비 작업.

application(_:didFinishLaunchingWithOptions:)

  • 앱이 완전히 로드되고 사용자 인터페이스가 준비된 후 호출됩니다.
  • 앱 설정이나 초기 데이터 로딩이 완료된 후 필요한 작업을 수행합니다.
  • 예시: 앱의 첫 화면 구성, 외부 리소스에서 데이터 페치, 앱 첫 실행 판단 등.

applicationWillResignActive(_:)

  • 앱이 active 상태에서 inactive 상태로 전환될 때 호출됩니다. 예를 들면 전화나 SMS 메시지를 받을 때입니다.
  • 앱이 잠시 중단되기 전에 실행 중인 작업을 일시 중단하거나 중요 데이터를 저장합니다.
  • 예시: 타이머 중지, 게임의 일시 중지 화면 표시 등.

applicationDidEnterBackground(_:)

  • 앱이 background 상태가 될 때 호출됩니다. 여기서 데이터를 저장하거나 네트워크 요청을 중지하는 등의 작업을 수행할 수 있습니다.
  • 예시: 사용자 데이터 저장, 백그라운드 작업 실행, 위치 추적 중지 등.

applicationWillEnterForeground(_:)

  • 앱이 background에서 foreground로 전환될 준비를 할 때 호출됩니다.
  • 앱이 다시 활성화되기 전에 필요한 작업을 수행합니다.
  • 예시: UI 업데이트, 중단된 작업 재시작 등.

applicationDidBecomeActive(_:)

  • 앱이 active 상태가 될 때 호출됩니다.
  • 사용자 인터페이스 업데이트나 중단된 작업 재개와 같은 작업을 수행합니다.
  • 예시: 게임 재시작, UI 갱신, 사용자 위치 추적 재개 등.

applicationWillTerminate(_:)

  • 앱이 종료되기 전에 호출됩니다.
  • 앱 종료 전 마지막으로 필요한 저장 작업이나 자원 해제 작업을 수행합니다.
  • 예시: 사용자 데이터 또는 게임 상태 저장, 네트워크 연결 종료, 메모리 해제 등.

SwiftUI

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 상태로 전환된다. 다른 앱을 선택하거나 홈 화면으로 돌아가면 현재 앱은 백그라운드 상태로 전환된다.

  • 로컬 알림 또는 푸시 알림: 앱을 사용하는 중에 알림이 도착하면 알림 배너가 상단에서 나타나며, 이 동안 앱은 In-Active 상태가 된다. 사용자가 알림을 터치하여 해당 알림과 관련된 다른 액션을 취하면 앱은 백그라운드로 전환될 수 있다.

  • Control Center 및 Notification Center: 사용자가 Control Center나 Notification Center를 스와이프하여 활성화하면, 앱은 In-Active 상태로 전환된다.

  • 스크린샷: 사용자가 스크린샷을 캡처하는 동안 앱은 잠시동안 In-Active 상태가 된다.

In-Active와 Background를 대비하는 동작

  • 데이터 저장: 앱이 In-Active나 Background 상태로 전환될 때, 진행 중인 작업이나 유저 데이터를 안전하게 저장한다. 이는 앱이 갑자기 종료되더라도 데이터 손실을 방지할 수 있다.

  • 네트워크 작업 일시 중지: 앱이 Background 상태로 전환될 때 활성화된 네트워크 작업을 일시 중지하거나 완료할 수 있다.

  • 백그라운드 작업 시작: 앱이 백그라운드에서 계속 작업을 수행해야 하는 경우 (예: 위치 추적, 파일 다운로드), 백그라운드 작업을 시작할 수 있다.

  • 리소스 해제: 메모리 제한이나 다른 리소스 제한을 고려하여 앱이 Background 상태로 전환될 때 불필요한 리소스를 해제한다.

  • UI 업데이트 일시 중지: 앱이 Background 상태로 전환될 때는 UI 업데이트가 필요하지 않으므로, 이를 일시 중지할 수 있다.

  • 알림 관리: 백그라운드에서 일어난 이벤트에 따라 사용자에게 로컬 알림을 보낼 수 있다.

  • 상태 복원: 앱이 다시 전경으로 전환될 때 이전 상태로 복원될 수 있도록 관련 데이터나 상태를 저장한다.

  • 보안: 보안이 중요한 앱의 경우, 앱이 In-Active나 Background 상태로 전환될 때 화면을 잠글 수 있다.

  • 타이머나 스케쥴러 관리: 활성화된 타이머나 스케쥴러가 있다면 앱의 상태 변화에 따라 이를 일시 중지하거나 재개할 수 있다.

  • 오디오 세션 관리: 오디오 플레이백이나 녹음 기능이 있는 앱의 경우, 앱의 상태 변화에 따라 오디오 세션을 관리한다.

profile
iOS 개발자😺

0개의 댓글