[WWDC18] Behind the Scenes of the Xcode Build Process
Behind the Scenes of the Xcode Build Process
Build System
Build Process
Build System -> Clang and Swift -> Linker
- 빌드 프로세스는 일련의 테스크를 실행하는 것
Build Task Execution Order
Build Task Dependency Order
- Build tasks are executed in dependency order
Build Process Graph
Discovered Dependencies
Change Detection and Task Signatures
• 각 테스크는 시그니쳐를 갖고 있다. • 빌드 시스템은 이전과 현재 빌드의 각 테스크 시그니쳐를 트래킹하고 있다. • 하나의 테스크 실행을 결정하기위해 시그니쳐들을 비교한다.
How can you help the build system?
• 각 태스크의 실행 순서보다는 태스크 디펜던시에 대해 생각해봐야 한다.
좀 더 빠른 빌드를 위해..
-
Declare Inputs and Outputs
-
Avoid Auto-Link for Project Dependencies
-
Add Explicit Dependencies
Clang Builds
What Is Clang?
Apple’s official compiler for the C language family • C • C++ • Objective-C • Objective-C++
Clang’s Two Feature
- Header Maps : Xcode빌드시스템과 clang 컴파일러간의 연결
- Clang Module : Clang speep up(header를 찾는 효율성 증가)
What Are Header Maps?
Common Project Issues with Header Maps
• 해더는 프로젝트의 일부가 아니다 • 같은 이름의 헤더들을 충돌없이 구분해야 한다
How do we find system headers?
Clang Modules
• On-disk cached header representation • Reusable • Faster build times
Module map
- Foundation Framework
// Module Map - Foundation.framework/Modules/module.modulemap framework module Foundation [extern_c] [system] { umbrella header "Foundation.h" export * module * { export * } explicit module NSDebug { header "NSDebug.h" export * } }
// Foundation.h ... #import <Foundation/NSScanner.h> #import <Foundation/NSSet.h> #import <Foundation/NSSortDescriptor.h> #import <Foundation/NSStream.h> #import <Foundation/NSString.h> #import <Foundation/NSTextCheckingResult.h> #import <Foundation/NSThread.h> #import <Foundation/NSTimeZone.h> ...
Module Cache
Swift Builds
Swift Does Not Have Headers
• 해더가 없기때문에 초보자들이 쉽게 시작할 수 있다 • 분리된 파일들에서 헤더선언이 중복되는 것을 피할 수 있다 • 컴파일러가 선언부분을 찾는것이 용이하다
Finding Declarations Within a Swift Target
- 컴파일러는 타겟안의 모든 파일을 대상으로 한다.
- Xcode 9: Repeated Work in Debug Builds • 각 파일을 분리해서 컴파일 • 페럴러하게 컴파일 하지만, 점점 복잡도가 증가 • 컴파일러는 선언한 부분을 찾기위해 반복적으로 파싱할수 밖에 없다
- Xcode 10: More Sharing in Debug Builds (NEW) • 여러 파일을 그룹화하여 공통의 컴파일 프로세스에 넣는다 • 하나의 프로세스안에서는 파싱한 부분을 공유한다 • 오직 프로세스간의 이동에서만 파싱을 반복한다
Finding Declarations from Objective-C
- 컴파일러는 Clang을 통해 Objective-c를 라이브러리화 한다
- Objective-C Declarations Come from Headers
- Imported Objective-C frameworks:
- Within frameworks that mix Swift and Objective-C
- Within applications and unit test bundles
- Imported Objective-C frameworks:
- Clang Importer Makes Methods More “Swifty”
- Rename Methods Based on Part of Speech
Generating Interfaces to Use in Objective-C
- Compiler Generates Objective-C Header
- Changing the Class Name in Objective-C
Linking
The Linker
- 마지막 테스크는 실행가능한 목적파일들을 빌드 하는 것
- 모든 컴파일러의 결과물을 하나의 파일로 결합함
- 두가지 종류의 파일들이 사용됨 • Object files (.o) • Libraries (.dylib, .tbd, .a)
Building Faster in Xcode
- 빌드 효율성 증가
- 리빌드 워크 감소
Increasing Build Efficiency
1. Parallelizing your build process
Game Dependency
- List of all targets to build
-
Dependency between targets
- Serialized Build TimeLine
Parallelized Build Timeline
- 빌드의 양은 줄지 않는다
- 빌드하는 시간만 줄어든다
- 하드웨어의 이용이 증가한다
Parallelized Target Build Process (NEW)
- OLD : Target Build Process
- NEW : Parallelized Target Build Process
- 소스 컴파일이 일찍 시작된다
- 필요한 부분만 기다린다
- Run Script phases는 꼭 기다려야 한다
2. Measuring your build time
Build With Timing Summary (NEW)
3. Dealing with complex expressions
a. Use Explicit Types for Complex Properties
- before
struct ContrivedExample { var bigNumber = [4, 3, 2].reduce(1) { soFar, next in pow(next, soFar) } }
- after (: Double)
struct ContrivedExample { var bigNumber: Double = [4, 3, 2].reduce(1) { soFar, next in pow(next, soFar) } }
b. Provide Types in Complex Closures
- before
func sumNonOptional(i: Int?, j: Int?, k: Int?) -> Int? { return [i, j, k].reduce(0) { soFar, next in soFar != nil && next != nil ? soFar! + next! : (soFar != nil ? soFar! : (next != nil ? next! : nil)) } }
- after
func sumNonOptional(i: Int?, j: Int?, k: Int?) -> Int? { return [i, j, k].reduce(0) { (soFar: Int?, next: Int?) -> Int? in soFar != nil && next != nil ? soFar! + next! : (soFar != nil ? soFar! : (next != nil ? next! : nil)) } }
c. Break Apart Complex Expressions
func sumNonOptional(i: Int?, j: Int?, k: Int?) -> Int? {
return [i, j, k].reduce(0) { soFar, next in
if soFar != nil && next != nil {
return soFar! + next!
}
if soFar != nil {
return soFar!
}
if next != nil { return next! }
return nil
}
}
func sumNonOptional(i: Int?, j: Int?, k: Int?) -> Int? {
return [i, j, k].reduce(0) { soFar, next in
if let soFar = soFar {
if let next = next {
return soFar + next
}
return soFar
} else {
return next
}
}
}
d. Use AnyObject Methods and Properties Sparingly
- 프로토콜 정의
Reducing the Work on Rebuilds
1. Declaring script inputs and outputs (Run Script Phases)
2. Understanding dependencies in Swift
a. Incremental Builds Are File-Based
b. Dependencies Within a Target Are Per-File
c. Cross-Target Dependencies Are Coarse-Grained
d. Swift Dependency Rules
- 컴파일러는 보수적
- 함수안에 내용이 바뀌어도 파일 인터페이스에는 영향을 안줌
- 모듈안의 디펜던시는 파일 단위
- 디펜던시간의 across 타겟은 전체 타겟
3. Limiting your Objective-C/Swift interface
a. Mixed-Source App Targets
b. Keep Your Generated Header Minimal (swift)
- Use private when possible
- Use block-based APIs
- Migrate to Swift 4
- Turn off “Swift 3 @objc Inference”
c. Keep Your Bridging Header Minimal (objective-c)
- Use categories to break up your interface
- before
- after
d. Less Content, Fewer Changes, Faster Builds
- 더 적은 컨텐츠, 더 적은 수정이 빌드를 빠르게 한다.
Leave a comment