Xcode 프로젝트의 빌드 시간이 길어지면 개발 사이클 전체가 느려집니다. 편집팀이 관찰한 다양한 프로젝트에서 실제 효과가 있었던 빌드 시간 단축 방법들을 정리합니다. 프로젝트마다 상황이 달라 모든 방법이 항상 도움이 되지는 않지만, 시도해볼 만한 방향을 제시합니다.
측정이 우선
빌드 시간 최적화의 첫 단계는 측정입니다. 어디에 시간이 걸리는지 모르면 어떤 변경이 실제 개선인지 판단할 수 없습니다.
Xcode의 빌드 로그에서 각 단계별 소요 시간을 확인할 수 있습니다. Product > Perform Action > Build With Timing Summary를 실행하면 빌드 완료 후 각 단계의 시간 요약이 표시됩니다.
더 세밀한 측정이 필요하면 Xcode 빌드 설정에서 진단 로그를 활성화합니다.
defaults write com.apple.dt.Xcode ShowBuildOperationDuration -bool YES
이 설정을 켜면 툴바에 빌드 시간이 표시됩니다. 각 빌드의 실제 소요를 즉시 확인할 수 있어 최적화 전후 비교에 유용합니다.
더 상세한 프로파일링은 XCLogParser 같은 도구로 가능합니다. 오픈소스로 배포되고 있고, Xcode 빌드 로그를 분석해 어떤 파일과 함수가 컴파일에 오래 걸리는지 상세히 보여줍니다.
Swift 컴파일러 경고 활성화
Swift 컴파일러는 특정 표현식이 컴파일에 오래 걸릴 때 경고를 표시할 수 있습니다.
# Build Settings에 다음 두 플래그 추가
-Xfrontend -warn-long-function-bodies=200
-Xfrontend -warn-long-expression-type-checking=200
200은 밀리초 단위. 이 시간을 초과하는 함수 본문이나 표현식 타입 검사에 경고가 표시됩니다.
실전에서 이 경고로 발견되는 것은 대개 복잡한 SwiftUI 뷰의 본문입니다. 여러 modifier가 체인으로 연결된 뷰나, 조건부 뷰가 많이 중첩된 경우 타입 추론에 시간이 오래 걸립니다.
// 컴파일이 오래 걸리는 예
var body: some View {
VStack {
if condition1 {
Text("A").font(.title).foregroundColor(.red)
} else if condition2 {
Text("B").font(.title).foregroundColor(.blue)
} else {
Text("C").font(.title).foregroundColor(.green)
}
// ... 반복
}
}
// 개선: 서브뷰로 분리
struct HeaderText: View {
let condition1: Bool
let condition2: Bool
var body: some View {
if condition1 {
Text("A").font(.title).foregroundColor(.red)
} else if condition2 {
Text("B").font(.title).foregroundColor(.blue)
} else {
Text("C").font(.title).foregroundColor(.green)
}
}
}
서브뷰로 분리하면 각 뷰의 타입 추론이 독립적으로 이루어져 전체 컴파일 시간이 크게 줄어드는 경우가 있습니다.
타입 명시
Swift의 타입 추론은 편리하지만 무겁습니다. 특히 컬렉션 리터럴이나 클로저에서 타입을 명시하면 컴파일러가 추론할 필요가 없어집니다.
// 추론이 오래 걸리는 코드
let items = [1, 2, 3].map { $0 * 2 }.filter { $0 > 2 }
// 타입을 명시
let items: [Int] = [1, 2, 3].map { $0 * 2 }.filter { $0 > 2 }
이 예제 자체는 사소하지만, 실제 프로젝트에서 복잡한 제네릭 타입이 여러 번 중첩되는 경우 타입 명시로 큰 컴파일 시간 절감이 가능합니다.
인크리멘탈 빌드 활용
Xcode는 기본적으로 인크리멘탈 빌드를 수행합니다. 변경된 파일과 그에 의존하는 파일만 다시 컴파일하는 방식.
인크리멘탈 빌드가 예상보다 많은 파일을 재컴파일하는 경우가 있습니다. 대개 의존성이 지나치게 넓게 퍼져 있기 때문입니다.
대표적인 경우: 앱 전체에서 사용하는 상수를 하나의 파일에 모두 넣는 것. 이 파일이 바뀌면 앱 전체가 재컴파일되어야 합니다. 상수를 도메인별로 나눠 여러 파일에 배치하는 편이 좋습니다.
또 다른 경우: 헤더 파일에 큰 구현을 포함하는 것. Objective-C 코드가 남아있는 프로젝트에서 특히 문제. 헤더에는 인터페이스만 두고 구현은 .m 또는 .mm에 두는 원칙을 지키는 것이 중요합니다.
모듈화
큰 앱을 여러 프레임워크나 Swift Package로 나누면 모듈별 인크리멘탈 빌드가 가능해집니다.
모듈화는 빌드 시간뿐 아니라 아키텍처적 이점이 있지만, 초기 비용이 큽니다. 기존 앱을 모듈화하는 것은 상당한 리팩터링을 요구합니다.
새 프로젝트를 시작할 때는 처음부터 모듈화된 구조를 고려하는 것이 나중에 유리합니다. 기능 도메인(예: Authentication, Networking, Editor, ContentStore)별로 별도 모듈로 나누는 방식.
빌드 캐시
Xcode 15부터 실험적으로 도입된 Explicitly Built Modules는 빌드 캐시의 재사용성을 크게 개선했습니다. 이 기능이 프로젝트에서 활성화되어 있는지 확인하고, 아직이라면 도입을 검토할 만합니다.
더 큰 프로젝트나 팀 환경에서는 Bazel, Buck2 같은 외부 빌드 시스템을 도입하는 경우도 있습니다. 이런 시스템은 원격 캐시를 지원해 팀원 사이에서 빌드 결과를 공유할 수 있습니다.
Bazel/Buck2 도입은 상당한 초기 투자가 필요하고, Xcode의 표준 흐름에서 벗어나는 부담이 있습니다. 대규모 프로젝트가 아니라면 표준 Xcode 흐름을 유지하는 편이 낫습니다.
DerivedData 관리
Xcode의 DerivedData는 빌드 산출물 캐시 저장소입니다. 이 폴더가 손상되거나 오래되면 예상치 못한 재컴파일이 발생합니다.
문제 해결을 위해 DerivedData를 삭제하는 것은 자주 하는 조치입니다. 하지만 반복적으로 필요하다면 원인을 찾아야 합니다.
일반적인 원인: 프로젝트 파일(.xcodeproj) 자체가 손상되었거나, 의존성 그래프에 순환이 있거나, Xcode 버전 업그레이드 후 캐시 형식이 바뀌었거나.
DerivedData 폴더 위치는 ~/Library/Developer/Xcode/DerivedData. Xcode Preferences > Locations에서 위치를 변경할 수 있습니다. SSD가 아닌 디스크에 두면 빌드가 느려집니다.
병렬 빌드 설정
Xcode는 기본적으로 CPU 코어 수에 맞춰 병렬 빌드를 수행합니다. 하지만 특정 설정이 병렬 실행을 제한할 수 있습니다.
Build Settings의 “Enable Parallel Builds” 옵션이 켜져 있는지 확인. Target 간 의존성이 있으면 순차적으로 빌드되어야 하지만, 독립적인 Target들은 병렬 실행이 가능합니다.
M1 이상의 Apple Silicon Mac에서 병렬 빌드의 효과가 특히 큽니다. 코어 수가 많고, 각 코어의 성능이 좋아 여러 파일이 동시에 빠르게 컴파일됩니다.
테스트와 빌드 분리
테스트 실행 시 테스트 대상 앱도 함께 빌드됩니다. 테스트 코드만 변경했는데 앱 전체가 재빌드되는 것은 낭비입니다.
Xcode 스킴 설정에서 테스트 단계의 빌드 대상을 세밀하게 조정할 수 있습니다. 테스트 실행 시 앱 빌드가 필요하지 않다면 스킴에서 이 단계를 제거하거나 조건화할 수 있습니다.
더 나은 접근은 앱과 테스트 대상 코드를 별도 프레임워크로 분리하는 것. 테스트가 이 프레임워크에만 의존하면 앱 빌드를 트리거하지 않습니다.
Objective-C ↔ Swift 브릿징 최적화
Objective-C와 Swift가 혼재된 프로젝트에서 브릿징 헤더가 크면 컴파일 시간이 길어집니다. Swift가 브릿징 헤더를 처리하는 데 시간이 걸리고, 이는 매 컴파일마다 반복됩니다.
브릿징 헤더는 Swift에서 실제 사용하는 Objective-C 인터페이스만 포함하도록 최소화. 사용하지 않는 헤더를 포함하지 마세요.
Objective-C 클래스에 @class 전방 선언을 활용해 헤더 파일 간 의존성을 줄이는 것도 큰 프로젝트에서 효과적입니다.
DEBUG vs RELEASE 빌드 시간
DEBUG 빌드는 최적화가 비활성화되어 빠릅니다. RELEASE 빌드는 최적화 때문에 오래 걸립니다.
개발 중에는 DEBUG 빌드만 사용하고, RELEASE 빌드는 CI 서버에서 야간에 실행하는 방식이 일반적입니다. 로컬에서 RELEASE 빌드가 필요한 경우는 최종 프로파일링이나 릴리스 전 검증 때뿐입니다.
DEBUG 빌드도 특정 최적화 플래그로 더 빠르게 만들 수 있지만, 디버깅 경험이 나빠지는 트레이드오프가 있습니다. Swift Optimization Level을 “-Onone”으로 유지하는 것이 표준.
실전 우선순위
모든 최적화를 동시에 시도할 필요는 없습니다. 편집팀이 관찰한 기준으로 효과가 큰 순서:
1. Swift 컴파일러 경고를 활성화해 오래 걸리는 표현식 찾기. 대개 소수의 파일이 컴파일 시간의 대부분을 차지합니다.
2. 복잡한 SwiftUI 뷰를 서브뷰로 분리.
3. 앱 전체에 걸친 상수 파일이나 광범위한 의존성을 가진 파일 재구조화.
4. 모듈화 (큰 프로젝트에서만).
이 순서로 시도하면서 각 변경의 효과를 측정. 효과가 없는 최적화에 시간을 쓰지 않는 것이 중요합니다.
참고 자료
WWDC 2023의 “Explore advances in Swift-DocC” 세션과 여러 Swift 성능 세션이 관련 배경을 제공합니다.
Swift Forums의 Compiler Development 카테고리에 컴파일러 성능 관련 논의가 자주 오갑니다.
XCLogParser GitHub 저장소의 문서에 빌드 로그 분석 방법이 상세히 나와 있습니다.