주간 오류 해결 모음집(25.02.10-15)
개요
이 글은 한 주 동안 프로젝트 코드를 작성하면서 발생했던 문제들을 기록하는 글이다
- 에러 로그 분석
- 앱 버전 동적 기록 방법
- 컴파일 에러(Multiple commands produce...)
- TabView ScrollView 버그
에러 로그 분석
Console.app
코드를 작성하고 빌드 후, 실행해 보니 Console.app을 통해 로그를 확인하라고 한다
Console.app은 macOS에 내장된 로그 뷰어 애플리케이션으로, 시스템 로그와 앱 로그를 실시간으로 모니터링하고 분석할 수 있는 도구다
아래의 이미지처럼, 로그 캡쳐 시작 및 정지/중지가 가능하며 이때 특정 키워드 관련 로그만 필터링도 가능하다
이렇게 실행해보니 'Failed to get FirebaseApp instance...'라는 로그를 확인할 수 있었다
아예 앱이 실행되기 전에 발생하는 오류다보니, 이렇게 에러 로그를 통해 에러 원인을 확인할 수 있다
그럼 해당 오류는 왜 발생했는가? 이것은 SwiftUI의 AppDelegate가 iOS와 macOS에서 돌아가는 순서가 약간 다르기 때문이다
iOS에서 FirebaseSDK를 사용하기 위해 설정을 하는 예시는 다음과 같다
이렇게 작성하면, FirebaseApp.configure는 하위 뷰에서 관련 라이브러리가 사용되기 전에 초기화가 진행된다
import SwiftUI
import FirebaseCore
@main
struct MyMacApp: App {
// NSApplicationDelegateAdaptor를 사용해 AppDelegate 연결
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
// AppDelegate 클래스 정의
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
// Firebase 초기화
FirebaseApp.configure()
}
}
하지만 같은 방식인 것 같지만, 아래와 같이 macOS가 타겟인 앱은 이렇게 코드를 작성하면, 위처럼 App에서 AppDelegate로 초기화를 위임해도, ContentView 내에서 Firestore를 FirebaseApp.configure()가 수행되기 전에 수행되면서 오류가 발생할 수 있다
import SwiftUI
import FirebaseCore
import FirebaseFirestore
@main
struct MyMacApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView() // Firebase 초기화 전에 Firestore 사용
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
// Firebase 초기화
FirebaseApp.configure()
}
}
struct ContentView: View {
// Firestore 인스턴스 생성
let db = Firestore.firestore() // Firebase가 초기화되기 전에 접근
var body: some View {
Text("Firestore 예제")
.onAppear {
// Firestore 사용
db.collection("example").getDocuments { snapshot, error in
if let error = error {
print("Error fetching documents: \(error)")
} else {
print("Documents fetched: \(snapshot?.documents.count ?? 0)")
}
}
}
}
}
그래서 그냥 아래와 같이 App의 생성자에서 FirebaseApp.configure()를 수행시키면 오류가 발생하지 않는다
IDEPreferLogStreaming
IDEPreferLogStreaming은 Xcode의 내부 설정 키 중 하나로, 디버깅 시 로그를 파일에 저장하는 대신 실시간으로 스트리밍하여 출력하도록 선호하는 옵션을 제어한다
즉, 디버깅 중 대량의 로그를 생성하거나 실시간으로 빠르게 로그를 분석해야 할 때 사용된다
스트리밍을 통해 저장된 로그를 읽고 처리하는 데 걸리는 지연 시간을 줄여, 디버깅 속도를 향상시킨다
프로젝트를 진행하며 WidgetExtension을 만들었는데, 오류가 발생했지만 time out이 발생해서 로깅을 초기화하는데 문제가 생겼다
이때 IDEPreferLogStreaming를 통해 에러를 스트리밍 했다
아래 사진처럼 Manage Scheme -> Run -> Arguments -> Environment Variables에 IDEPreferLogStreaming=YES를 등록한다
참고로 에러는 등록된 위젯에서 해당 위젯을 찾을 수 없었다는 오류였다..
앱 버전 동적 기록 방법
앱의 빌드 과정에서 소스 코드와 리소스를 컴파일하고, 최종적으로 아카이브와 추출 과정을 거쳐 배포 가능한 형태로 패키징되며, 이때 앱의 메타데이터와 설정 정보를 담고 있는 Info.plist 파일이 포함되는데, 특히 CFBundleShortVersionString(앱 버전)과 CFBundleVersion(빌드 번호)은 앱의 버전 관리와 업데이트 추적에 중요한 역할을 한다
그렇기 때문에 Info.plist에 해당 내용을 수정하고 빌드를 진행하면, 만들어진 앱의 버전 및 빌드 번호를 동적으로 빌드 시에 결정할 수 있다
하지만 이보다 더 간단하게 xcodebuild 명령어에 인자로 CURRENT_PROJECT_VERSION과 MARKETING_VERSION을 통해 설정할 수도 있다
컴파일 에러 - Multiple commands produce...
Multiple commands produce 컴파일 오류는 동일한 파일이 프로젝트에 존재해서 컴파일 시에 발생하는 오류다
필자처럼 git을 이상하게 사용하다 보면, 이런 문제가 발생하는 경우도 있을 수 있다..
그런 경우에 다음과 같이 TARGETS -> Build Phases -> Complile Source에서 필요 없는 중복파일을 제거하면 된다
TabView ScrollView 버그
macOS 타겟 앱에서 .windowStyle(.hiddenTitleBar)를 통해 상단 타이틀바를 없앴을 때, TabView에서 ScrollView를 사용하면 아래 사진처럼 빈 공간을 뚫고 나간다
여러가지 시도를 해봤는데 다음과 같이 높이가 0짜리인 View를 위치시키면, 해당 부분을 뚫고 나가지 않는다
var body: some View {
NavigationSplitView(
columnVisibility: $columnVisibility,
sidebar: {
List(selection: $selectedItem) {
sidebar
}
},
detail: {
switch selectedItem {
case .home:
home
case .profile:
profile
}
})
}
@ViewBuilder
private var home: some View {
/// .windowStyle(.hiddenTitleBar) 버그로 인해 추가
Color.clear.frame(height: 0)
ScrollView {
VStack {
ChatBoardView(viewModel: .init(container: container,
userInfo: viewModel.userInfo))
TodoBoardView(viewModel: .init(container: container,
userInfo: viewModel.userInfo))
}
.padding(.horizontal)
}
}
@ViewBuilder
private var sidebar: some View {
...