clyne의 개발 기록

[Xcode Build System] Build 버튼을 눌렀을 때 어떤 일이 일어날까? 본문

iOS

[Xcode Build System] Build 버튼을 눌렀을 때 어떤 일이 일어날까?

clyne_dev 2022. 3. 3. 22:52

글은...

Xcode에서 빌드하기를 눌렀을 , 어떤 일들이 일어나는지 이해하고,

빌드시 발생하는 에러를 수월하게 찾아내고자, 그리고 빌드 속도에 영향을 주는게 무엇일까..  가 궁금해서 공부했던 경험을 공유하기 위해 작성되었습니다.

 


Language Processing System

컴퓨터시스템은 하드웨어와 소프트웨어로 분류되는데요,

이중 소프트웨어는 프로세스들이 어떻게 동작할지 편성하고, 하드웨어는 동작을 수행합니다.

 

그러나, 0과 1만 이해할 수 있는 하드웨어는  고수준 언어인 Swift 직접적으로 이해하지 못합니다.

그렇기 때문에 Swift는 하드웨어가 이해할 수 있는 기계어로 변환되어야하는데, 그 작업을 수행해주는 시스템을

< 언어 처리 시스템 (Language Processing System) > 이라고 합니다.

여러 언어처리시스템 중, 우리가 매일 iOS를 개발할 때 사용하는 언어처리시스템이  바로  Xcode Build System 입니다.

 

 

Xcode Build System 

Xcode 빌드는 크게 다섯 단계로 분류됩니다.

  • Preprocessor
  • Compiler
  • Assembler
  • Linker
  • Loaded

 

위와같은 단계들을 다이어그램으로 나타내면 다음과 같습니다.

 

&amp;amp;amp;nbsp; https://www.vadimbulavin.com/xcode-build-system/&amp;amp;amp;nbsp;

 

Preprocessing (전처리) 

전처리 단계는 우리가 작성한 소스코드를 컴파일 하기 전, 어떤 소스코드를 컴파일해야하는지 결정하여 변환하는 단계입니다.

대표적으로 아래와 같은 코드가 전처리 단계에서 처리됩니다.

#define

#include

#if DEBUG
print("DEBUG 에서만 컴파일 되는 코드")
#endif

#if os(iOS)
print("iOS에서만 컴파일 되는 코드")
#endif

참고로, swift컴파일러에는 전처리기가 없어서, Swift 프로젝트에서는 매크로를 정의할 수 없습니다.

(#define 이나 #include 등과 같이 C 또는 obj-C처럼 사용하지 못함)

그러나 Xcode Build System에서는 부분적으로 사용가능하게 제공하고 있습니다.

Xcode Build Settings -> Active Compliation Conditions 항목에서 세팅이 가능합니다.

Xcode는 전처리와 관련된 의존성을 < llbuild (Low-Level Build System) > 라는 라이브러리로 해결하고있습니다.

위와같은 동작이 가능한 이유는 llbuild 라이브러리에서 제공하고 있기 때문입니다.

llbuild는 오픈소스이며, 애플에서 제공하고 있습니다.  (깃헙주소: https://github.com/apple/swift-llbuild)

(깃헙에서 소개하길  Shake, Buck, Ninja 라는 빌드시스템으로부터 영향을 많이 받았다는데,  다른 빌드시스템에 대해서는 나중에 공부해볼 예정입니다)

 

 

Compiler (컴파일러) 

컴파일 단계는 Swift, Obj-C 와 같은 고수준 언어로 작성된 소스코드를, (프로그래머의 의도를 잃지 않고) 기계어로 변환해주는 단계입니다.

 

Xcode는 두 가지의 컴파일러를 가지고 있습니다.

https://www.vadimbulavin.com/xcode-build-system/

1. clang: C언어 가족들 (Obj-C/C++, C/C++)을 위한 애플의 공식 컴파일러 입니다. clang또한 애플에서 제공하는 오픈 소스입니다.

(clang 오픈소스 깃헙 주소: https://github.com/apple/swift-clang)

2. swfitc: Swift용 컴파일러입니다.  (애플이 Swift를 개발했다는것은.. 컴파일러부터 만들었다는 소리니, 지금 생각해보니 애플 개발자분들 참 대단하네요..) swiftc 컴파일러 또한 애플에서 제공하는 오픈 소스입니다. Swift언어 자체가 오픈소스이니만큼, swift 깃헙에 포함되어있습니다.

(swiftc가 포함되어있는 swift 깃헙 주소 : https://github.com/apple/swift - 무려 921명의 컨트리뷰터 ㄷㄷ.. )

 

Xcode에  Build Settings에  이 두 컴파일러에 대한 설정이 각각 있는 것을 보실 수 있습니다.


컴파일러는 Front End 와 Back End 라고 하는 2개의 메인 파트로 나뉩니다. 

(여기는 아직 제가 이해하기가 힘들어서..  컴파일러에 대해 좀 더 깊게 공부하고나서 보충해보도록 하겠습니다.. ㅜㅜ)

 

1. Front End

프론트엔드 파트에서는 소스를 여러 조각으로 나누고, 문법 구조로 intermediate representation 을 생성합니다. (중간표현?)

그리고 각각의 조각(code 또는 data)의 symbol (조각의 이름)을 "symbol table"로 작성하고 관리합니다.

이 symbol table에  변수,함수,클래스의 이름을 저장하고있습니다. 각각의 symbol은 특정 조각(code or data조각) 에 매핑됩니다.

 

위에서 언급한 intermediate representation 은  swift 컴파일러에서는 Swift Intermediate Language (SIL) 이라고 부릅니다.

(뭔가 Xcode 빌드 로그에서 많이 봤던 용어네요...)

SIL은 코드분석 및 최적화에 사용된다고 하네요..

 

그리고 이 SIL은 LLVM intermediate representation으로 한번 더 변환된다고 하네요..

LLVM은  Low-Level Virtual Machine 의 약자로, 기계어로 변환해주는 저수준 가상머신... 이라고 합니다.

자세한 내용은 Zedd님 포스팅을 참고해주세요 (https://zeddios.tistory.com/1175)

 

2. Back End

백엔드 단계에서는  intermediate representation이 어셈블리어로 변환됩니다.

 

 

 

추가로, 변경사항이 있어, 새로 컴파일해야하는지 여부는 Task Signature를 관리함으로써 감지한다고 합니다.

Xcode build system이 현재 그리고 직전 빌드의 task signature를 추적하여 관리합니다.

여기서 알 수 있는 것은 모듈화를 통해 task를 계속해서 쪼개면, 변경사항이 없는 task는  스킵하므로써, 빌드속도를 더 빠르게 할 수 있습니다.

 

 

 

Assembler (어셈블러) 

어셈블러는 사람이 읽을 수 있는 어셈블리코드를 기계어로 변환해주는 역할을 수행합니다.

이 어셈블러는 code 또는 data의 집합체인 Mach-O 라는 파일을 생성합니다.

 

Mach-O 파일은 iOS와 MacOS에서 쓰이는 특정 파일 포맷입니다.

object files, 실행파일, 라이브러리에  이 파일 포맷을 사용하게 됩니다.

이 Mach-O는 iOS기기의 ARM 프로세서  및  Mac 기기의 인텔 프로세서 (CPU) 에서 실행될 수 있는 그룹화된 바이트 스트림입니다.

 

실제로 빌드하고나면, 

~/Library/Developer/Xcode/DerivedData/{빌드한프로젝트명}/Build/intermediates.noindex/{프로젝트명}.build/Debug-iphonesimulator/{프로젝트명}.build/Objects-normal/x86_64 

경로에  Mach-O 파일을 찾아보실 수 있습니다.

RootViewController.o 파일이 Mach-O 파일입니다.

 

storyboardc

(스토리보드는 storyboardc 라는 확장자로 생성되는군요...)

 

 

 

다른 파일은 뭔지 잘 모르겠네요.. 

hexdump 파일경로/RootViewController.o

터미널에서 위와같은 명령어를 입력하면,   확인해볼 수 있다네요...

 

 

Linker (링커) 

링커는 여러 object file들과 library들을 병합하여  하나의 실행가능한 Mach-O파일을 생성해주는 프로그램 입니다.

링커는 어셈블러 단계에서 나오는 object file이랑 여러타입의 라이브러리 (dylib, .tdd, .a) 들을 Input으로 받을 수 있습니다.

 

위의 어셈블리 단계에서도 Mach-O파일을 생성하고, 링커에서도 Mach-O파일을 생성하네요..?

두가지 차이에 대해 알아보겠습니다.

 

어셈블리 단계에서 생성되는 Object File은 아직 완성되지 않은 파일입니다.  다른 object file이나 library를 참조하는 조각을 누락했을 수 있기 때문입니다. 

이 작업(다른 object file 또는 library를 참조)을 링커가 해서 완성된 excutable Object File을 생성한다고 보시면 됩니다.

이 과정에서 에서 링커는 컴파일 단계에서 생성된 "symbol table"을 사용하는데요, 이 과정이 제대로 이루어지지 않았을 때는  우리가 빌드할 때 자주 접했던 "undefined symbol" 에러가 발생하게 됩니다.

 

 

 

Loader (로더) 

링커의 역할이 끝나면 .app 번들이 완성되게 됩니다.

로더는 이 프로그램을 메모리에 올리고 실행시키는 운영체제의 한 부분입니다.

프로그램이 동작할 수 있도록 메모리 공간을 할당하고, 초기 상태를 등록하는 역할을 수행합니다!

 

 

 

 


 

 

 

그렇다면 빌드할 때 개발자의 역할은..? (feat. WWDC 2018) 

WWDC 2018에서 발표한 Behind Scenes of the Xcode Build Process 세션에서  빌드 과정을 소개해줬는데요,

swift로 작성된 소스코드 뿐만 아니라, 스토리보드와 Assets 들도 컴파일 및 복사한다고 합니다.

 

WWDC 2018 - Behind Scenes of the Xcode Build Process 세션 3:37

PetWall 이라는 앱을 빌드하기 까지의 과정을 나타내는데,  한눈에봐도, 컴파일단계에서 많은 시간을 차지하는 것 같죠?

 

 

위에서 말씀드린 mach-o 파일을 생성하고,

 

 

링커를 통해 mach-o파일들을 연결하여 app 번들을 생성하게 됩니다!

 

 

각 빌드 Task 단위는  일종의 해시값인  Signature를 가집니다.  이 서명 값을 통해  빌드가 수행될 떄마다, 다시 컴파일을 해야하는지를 체크하여 필요없는 중복 작업을 줄입니다. 이것을 "Increment Build" (증분빌드)  라고 합니다.

 

 

Dependency (의존성)

자! 여기까지는 한 단위의 Build Task에 대한 내용이었고... 우리는 많은 의존성을 가진 앱을 빌드해야하잖아요?

그래서 이제부터 의존성에 대한 이야기가 주를 이루게 됩니다.

 

링커를 통해 실행가능한 파일을 생성하기 위해서는, 각 object file들이 어디에 의존을 하고있는지 알아야하고, 그것은 Xcode 내에서 자동화되어 알 수도 있고, 개발자가 설정할 수도 있습니다. 에를들면, 프레임워크와 같은 것들이죠

최상위 PetWall.app 번들은  다음과 같은 의존 그래프를 가집니다.

 

이 때, 가장 의존성이 없는 파일부터 하나씩 차례로 컴파일 하기에는 시간이 굉장히 오래걸리겠죠?

그래서 Xcode 빌드 시스템은 이것을 병렬처리할 수 있도록 개발되었다고 합니다. 아래와 같이요

(병렬처리 설정은 바로 다음 이미지에 있습니다)

 

 

A가 컴파일 되어야만, A에 의존하고 있는 B가 컴파일 될 수 있다면,   B는 A를 항상 기다려야 하지만, 

A와 C는 의존적이지 않다면, A와 C가 병렬로 컴파일될 수 있기 때문에, 빌드 시간이 단축됩니다.

 

병렬 작업 설정은 Xcode "Edit Scheme"메뉴에서 병렬처리 옵션에 체크해주면 됩니다. (Xcode 13에선 해당 설정이 없네요..  기본값 활성화로 추측됩니다... )

 

 

 

대신 커맨드 라인 툴 패키지로 빌드할 때의  병렬처리 빌드에 대한 옵션은 있습니다.

프로젝트 설정에 있네요. (커맨드 라인 툴로 빌드하는 것이 궁금하시면 여기를 방문해보세요!https://velog.io/@devapploper/Command-Line-Tool을-사용하여-Xcode-프로젝트를-빌드-및-테스트-하기)

 

 

 PetWall.app 번들의 의존그래프를, 빌드순서 그래프로 나타내면 다음과 같습니다.

 

Xcode는 이 의존성을 토대로 병렬처리를  통해 빌드속도를 개선하고 있습니다.

 

 

그렇다면 그 의존성은 어떻게 결정할까요?

 

1. 가장 기본적으로 제일먼저 Xcode Build System 에 구현된 룰의 내용에서 결정됩니다. 

여기있는 설정들인데, 보시면 swift는 뭘로 컴파일하고, C는 뭘로 컴파일하고, Nib, Storyboard는 뭘로 컴파일하고...  

예를들어 IB Storyboard Compiler에 .storyboard 가 input으로 들어가면 output으로 .storyboardc 파일이 나온다..  

이러한 빌드규칙들이 여기에 모두 정의되어있습니다.

 

2. 타겟 의존성 설정에 의해 결정됩니다.

xcode10 이전에는  해당 타겟이 빌드를 시작하기 전에, 모든 종속 대상의 컴파일이 완료되어야만 가능했습니다.

그런데, Xcode10 부터는 병렬처리의 효율성을 위해  암시적 종속성을 통해  더 빨리 병렬컴파일을 수행할 수 있다고 합니다.

 

암시적 종속성 (implicit dependency) 란,  명시적으로 코드 또는 설정에 종속성을 주지 않아도 해당 종속성을 통해 동작하는 것을 말합니다.  간단하게 예를들면...  Test.swift  파일에서  import RxSwift   를 하지 않고도  RxSwift 모듈을 사용할 수 있는 것은 암시적 종속성 때문이에요..   이 내용을 빌드과정에 적용시킨다고 생각하시면 돼요! 타겟 종속성에 명시적으로 추가하지 않아도, 암시적으로 해당 타겟 종속성을 찾는다는 이야기입니다.

 

빌드시 암시적 종속성을 찾는 것에 대한 설정은 "Edit Scheme"에 에서 체크가 가능합니다.

 

3. 빌드 단계 종속성

바로 이쪽 설정인데요, 

헤더 복사, 소스 컴파일, 번들 리소스 복사 등.

빌드 순서는 위에서부터 순차적으로 진행됩니다.

드래그해보시면 순서를 변경하실 수도 있는데, Xcode 빌드 시스템이  빌드 순서를 명확히 알고 있으면 (빌드 시스템의 로직에 따라..?) 그 순서가 변경될 수 있다고 합니다.

물론  이 순서가 잘못되었을 때, 빌드 문제나 실패가 발생할 수 있기때문에, 종속성을 이해하고 올바른 순서로 되어있는지 확인하는 것은 개발자의 몫입니다.

 

4. Scheme Order 종속성

다음으로 스킴 순서 종속성입니다. "Edit Scheme"쪽에서 설정하실 수 있는데요,,  

 

기본적으로 빌드 순서는 의존성 순서로 빌드하게 되는 것이고,

만약 의존성 순서로 빌드하지 않고, 내가 수동으로 위에서 아래로 순차적으로 빌드하고싶다!  하시면은...

"Edit Scheme"쪽으로 가셔서,  "Manual Order" 에 체크하시면 됩니다!

 

 

 

5. 개별 커스텀 종속성

마지막으로  커스텀 종속성을 설정할 수 있다고 하는데요...  어.....  이건  사실 정확히 어떨 때 사용해야하는지 잘 모르겠습니다.  죄송해요..........

앞으로 알게되면 내용 추가해볼게요!!

 

 

말미에  아래의  "Link Frameworks Automatically" 에 의존하지 말라고 이야기합니다.

어디까지나, 플랫폼 SDK (예를들면 UIKit or Foundation 등)의 프레임워크에 대해서만 보장하고, 다른 프레임워크는 꼭 명시적으로 종속성을 추가해야 한다고 하네요...

 

 


 

여기까지 Xcode 의 빌드 시스템에 대해 알아보았습니다.

비록 겉핥기 식이었지만...  전체적인 내용을 파악해볼 수 있었습니다.

 

 

 

 

 


 

 

참고자료 / 공부자료

 - (WWDC-2018: Behind Scenes of the Xcode Build Process) https://developer.apple.com/videos/play/wwdc2018/415/

 - (WWDC-2018 : Building Faster in Xcode) https://developer.apple.com/videos/play/wwdc2018/408

 - https://www.vadimbulavin.com/xcode-build-system/ 

 - https://eunjin3786.tistory.com/323

 - https://reakwon.tistory.com/52   

 - (LLVM 설명) https://zeddios.tistory.com/1175

'iOS' 카테고리의 다른 글

[iOS] Lifecycle of UIView  (0) 2022.03.12
[Private] iOS 면접 대비  (0) 2021.10.07
iOS 면접 질문들...  (0) 2021.10.04
[iOS] 앱스토어 리젝 조치1  (0) 2021.06.13