Xcode 빌드 시간 최적화: 느린 빌드를 3배 빠르게

모바일 개발

Xcode빌드 최적화iOSSwift 컴파일러CI/CD

이 글은 누구를 위한 것인가

  • 클린 빌드에 5분 이상 걸리는 iOS 팀
  • 코드 변경 후 증분 빌드가 느린 개발자
  • CI/CD 빌드 시간을 단축하고 싶은 팀

들어가며

Xcode 빌드가 느리면 개발자 생산성이 크게 떨어진다. 10분 빌드 × 하루 20번 = 200분 낭비다. 빌드 설정 최적화와 코드 패턴 개선으로 빌드 시간을 절반 이하로 줄일 수 있다.

이 글은 bluefoxdev.kr의 iOS 개발 생산성 가이드 를 참고하여 작성했습니다.


1. 빌드 시간 병목 분석

[빌드 시간 측정 방법]

1. Xcode Build Timing Summary:
   Product > Perform Action > Build with Timing Summary
   각 파일 컴파일 시간 확인

2. 명령행 측정:
   xcodebuild -workspace App.xcworkspace \
     -scheme App -configuration Debug \
     SWIFT_COMPILATION_MODE=wholemodule 2>&1 | \
     grep -E "^CompileSwift|^Libt"

3. BuildTimeAnalyzer 도구:
   오래 걸리는 함수 타입 추론 시각화

[일반적 빌드 시간 병목]

1. 느린 타입 추론 (50% 이상)
   복잡한 클로저, 긴 배열 리터럴
   해결: 타입 명시

2. 매크로 처리
   @Observable, @Model 등
   해결: 별도 타겟으로 분리

3. 전체 재컴파일 (헤더 변경)
   Objective-C/Swift 브릿지
   해결: 최소화

4. 패키지 빌드
   SPM, CocoaPods
   해결: 이진 의존성 (XCFramework)

[빌드 최적화 체크리스트]
  □ Debug 빌드에서 최적화 끄기
  □ Whole Module Optimization 이해
  □ 불필요한 타겟 제거
  □ 증분 빌드 활성화
  □ 빌드 캐시 설정

2. 빌드 최적화 설정

# Xcode Build Settings 최적화

# 1. Compilation Mode
# Debug: Incremental (빠른 증분 빌드)
SWIFT_COMPILATION_MODE = incremental

# Release: Whole Module (최적화)
# SWIFT_COMPILATION_MODE = wholemodule

# 2. 타입 체커 제한 (느린 타입 추론 감지)
# Swift Other Flags:
OTHER_SWIFT_FLAGS = -warn-long-function-bodies=100 -warn-long-expression-type-checking=100

# 3. 병렬 빌드
SWIFT_ENABLE_BATCH_MODE = YES

# 4. Module Verifier 비활성화 (Debug)
ENABLE_MODULE_VERIFIER = NO
// 느린 타입 추론 패턴과 해결

// ❌ 느림: 복잡한 타입 추론
let result = items
    .filter { $0.isActive && $0.count > 0 }
    .map { item in
        (id: item.id, value: item.value * 2.5, label: item.name + " (\(item.count))")
    }
    .sorted { $0.value > $1.value }

// ✅ 빠름: 타입 명시
let filtered: [Item] = items.filter { $0.isActive && $0.count > 0 }
let mapped: [(id: String, value: Double, label: String)] = filtered.map { item in
    (id: item.id, value: item.value * 2.5, label: "\(item.name) (\(item.count))")
}
let result: [(id: String, value: Double, label: String)] = mapped.sorted { $0.value > $1.value }

// ❌ 느림: 큰 딕셔너리 리터럴
let config = [
    "key1": "value1", "key2": "value2", "key3": "value3",
    "key4": "value4", "key5": "value5", "key6": "value6",
    // ... 수십 개
]

// ✅ 빠름: 분리
var config: [String: String] = [:]
config["key1"] = "value1"
config["key2"] = "value2"
// 또는 struct 사용

struct Item {
    let id: String
    let name: String
    let value: Double
    let count: Int
    let isActive: Bool
}
#!/bin/bash
# CI 빌드 캐싱 스크립트

# 1. Derived Data 캐싱
DERIVED_DATA_PATH="$HOME/Library/Developer/Xcode/DerivedData"
CACHE_KEY=$(shasum Package.resolved Podfile.lock 2>/dev/null | md5)

# 캐시 복원
if [ -d "build-cache/$CACHE_KEY" ]; then
    echo "캐시 복원 중..."
    cp -R "build-cache/$CACHE_KEY" "$DERIVED_DATA_PATH"
fi

# 빌드 실행
xcodebuild \
    -workspace App.xcworkspace \
    -scheme App \
    -configuration Debug \
    -derivedDataPath "$DERIVED_DATA_PATH" \
    -destination "platform=iOS Simulator,name=iPhone 15" \
    build | xcbeautify

# 캐시 저장
echo "캐시 저장 중..."
mkdir -p "build-cache"
cp -R "$DERIVED_DATA_PATH" "build-cache/$CACHE_KEY"

# 2. SPM 패키지 캐싱
# Package.resolved를 캐시 키로 사용
SPM_CACHE_PATH="$HOME/Library/Caches/org.swift.swiftpm"
SPM_CACHE_KEY=$(shasum Package.resolved | cut -d' ' -f1)

# GitHub Actions 예시
# - name: Cache SPM
#   uses: actions/cache@v3
#   with:
#     path: ~/Library/Caches/org.swift.swiftpm
#     key: ${{ runner.os }}-spm-${{ hashFiles('Package.resolved') }}

# 3. XCFramework 이진 의존성 (패키지 빌드 완전 스킵)
# 자주 변경되지 않는 의존성을 미리 컴파일
# 예: firebase-ios-sdk XCFramework 사용
echo "빌드 완료"
// 모듈 분리로 재컴파일 최소화

// Package.swift에서 모듈 분리
// let package = Package(
//     name: "MyApp",
//     targets: [
//         .target(name: "Core", dependencies: []),
//         .target(name: "Features", dependencies: ["Core"]),
//         .target(name: "App", dependencies: ["Core", "Features"]),
//     ]
// )

// Preview 매크로 비용 최소화
// SwiftUI Preview는 별도 컴파일 필요
// → Preview 없이도 동작하게 하거나 별도 타겟

struct ContentView: View {
    var body: some View {
        Text("Hello")
    }
}

// Preview를 조건부로
#if DEBUG
#Preview {
    ContentView()
}
#endif

마무리

Xcode 빌드 최적화의 80%는 코드에 있다. -warn-long-expression-type-checking=100으로 100ms 이상 걸리는 표현식을 찾아 타입을 명시하면 증분 빌드가 30-50% 빨라진다. CI 빌드는 SPM 캐시와 DerivedData 캐싱으로 클린 빌드 시간을 2-3배 단축할 수 있다.