[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