iyOmSd/Title: Swift

[Swift] WWDC24 Demystify explicitly built modules

냄수 2024. 11. 26. 17:24
반응형

Demystify explicitly built modules 세션을 정리한 글입니다

 

Modules

기본적으로 모듈은 코드 배포의 단위이며 라이브러리나 프레임워크의 인터페이스를 설명해 줍니다 

Swift 대상에는 모듈을 구성하는 여러 Swift 파일이 포함되고 일반적으로 단일 대상이나 프레임워크의 모든 Swift 소스 파일은 동일한 모듈에 포함됩니다

모듈의 인터페이스는 Swift에 액세스 지정자로 명시적 표시됩니다

클래스와 해당 변수가 public으로 표시되어 임포터에게 보여집니다

 

모듈은 다른 모듈을 가져올 수도 있으며 전체 프로젝트에서 비순환 모듈 그래프를 형성합니다 

Swift 컴파일러는 사용자가 작성한 외부 인터페이스를 가져와 인터페이스만 포함된 텍스트 형식의 .swiftinterface 파일로 요약합니다

 

Objective-C에 있는 모듈은 다르게 표현됩니다 

Swift와 달리 C 언어 제품군에서는 모듈의 인터페이스가 수기로 작성됩니다

잘 알려진 헤더 개념으로 시작하여 헤더에서 모듈이 구성되는 방식을 설명해 주는 모듈 맵이라는 파일을 옆에 추가합니다

화면은 UIKit 모듈의 예입니다

UIKit의 헤더로 시작한 다음 UIKit 모듈 맵을 추가합니다

이 모듈 맵은 UIKit 모듈에 대한 여러 정보를 컴파일러에 전달합니다

framework module UIKit <- 이것은 프레임워크 모듈이고 이름이 UIKit이라는 것을 나타냅니다 

이 이름이 @import가 작동하도록 해 주며 소스 코드 자체는 이름을 정의하지 않습니다

umbrella header <- 그런 다음 UIKit.h에는 이 모듈에 구성된 모든 헤더가 포함됩니다 

export * < - 마지막으로 이 모듈이 가져오는 모든 모듈은 UIKit을 가져오는 코드에서 사용할 수 있습니다

 

모듈을 사용하면 컴파일러가 다양한 소스 파일 전반에서 인터페이스의 구문 분석을 공유할 수 있습니다 

각 모듈을 독립적으로 컴파일링해 컴파일러가 프로젝트 소스를 컴파일할 때 읽을 수 있도록 바이너리 파일로 변환하고 참조마다 모듈 공용 인터페이스를 가져오는 식으로 작업이 진행됩니다.

Swift에서 이 컴파일된 형식이 *.swiftmodule 파일로 표시되며 Clang에서는 *.pcm이나 precompiled module file로 됩니다

 

하지만 이 재사용이 가능하려면 해당 모듈을 먼저 찾아서 컴파일해야 함

어떻게 이 작업이 이뤄질까요?

 

 

Using modules

컴파일러가 @import를 발견하면 먼저 해당 import가 참조하는 모듈을 발견하고 해당 모듈의 컴파일된 표현을 가져옵니다

컴파일러가 @import UIKit을 식별하면 먼저 SDK에서 UIKit의 모듈 맵을 찾습니다

 

이제 UIKit 모듈에 대해 알아봤으니 UIKit에서 컴파일된 .pcm 파일을 찾아야 합니다 

하지만 해당 파일이 이미 존재하지 않으면 어떻하죠?

 

[모듈을 만드는 두 가지 일반적인 방법과 Xcode 16의 차이점]

먼저 묵시적으로 빌드된 모듈이며 Xcode의 다른 부분에서는 이 모듈의 존재가 알려지지 않아도 컴파일러들이 서로 조정하며 모듈 빌드를 관리합니다 

Swift와 Clang이 도입된 이후 모듈 빌드가 진행되어 온 방식입니다.

프로젝트에 Swift, C Objective-C 코드가 포함되면 묵시적 빌드 모듈을 사용하지만 빌드에는 종종 여러 장기적인 실행 작업이 포함됩니다

 

각 행은 Xcode가 빌드 작업을 수행할 수 있는 개별 실행 라인을 나타냅니다 

빌드 시스템이 컴파일 작업을 시작하고 개별 컴파일러가 묵시적 방식으로 발견하고 먼저 도달한 경우 모듈을 빌드하거나 진행하면서 이미 존재하는 모듈을 로드합니다

 

타임라인 뷰에는 다음과 같이 표시될 수 있으며 빌드 시작 부분의 컴파일 작업이 끝부분 작업보다 더 오래 걸립니다
묵시적으로 빌드된 모듈을 사용하면 

묵시적으로 모듈을 빌드하는 한 번의 컴파일 작업으로 끝내면서 또 다른 작업이 해당 모듈을 필요로 하여 중단되고 빌드되기를 기다릴 수 있습니다 

이러한 상황이 빌드의 많은 부분 전반에 걸쳐 발생할 수 있습니다 

 

Xcode 16은 명시적으로 빌드된 모듈을 사용하여 Xcode가 컴파일러와 조정을 통해 모듈을 발견, 빌드하도록 변경합니다 

명시적으로 빌드된 모듈은 묵시적 모듈 빌드 작업을 진행하며 명시적 빌드 시스템 작업으로 이를 전환합니다

이렇게 하기 위해 Xcode는 각 소스 파일의 컴파일을 각 세 단계로 나눕니다 

스캔, 모듈 빌드, 원본 코드 빌드입니다

Xcode는 각 소스 파일 검사부터 시작하여 전체 프로젝트에 대한 모듈 그래프를 구성하고 대상 전체에서 모듈을 공유합니다 

모듈 그래프를 구성하면서 모듈 컴파일 작업 전달을 시작할 수도 있습니다 

다음은 빌드 로그의 명시적인 작업이며 필요한 컴파일된 모듈을 직접 제공받을 수 있습니다 

마지막은 원래 컴파일 작업의 실행 단계이며 필요한 컴파일된 모듈을 추가하도록 수정된 후 진행됩니다

 

명시적으로 빌드된 모듈의 경우 타임라인 뷰에 명시적인 스캔작업, 모듈 컴파일작업, 원본 소스 파일작업이 포함됩니다

그럼에도 시간은 크게 단축되었죠

 

모듈의 존재를 인식하는 빌드 시스템의 이점은 이제 실행 준비가 안된 작업으로 실행 라인을 채우는 것을 방지할 수 있다는 것입니다 

대신 모듈이 준비될 때까지 작업을 실행하지 않고 기다립니다 

이렇게 하면 빌드 시스템이 사용 가능한 실행 라인을 효과적으로 사용할 수 있습니다

 

모듈 빌드에 대한 이 접근 방식을 채택하면 몇 가지 이점이 있습니다 

첫째로 해당 빌드를 더 신뢰할 수 있습니다 

정확한 종속성과 빌드 시스템에 노출된 확정적인 빌드 그래프를 사용하여 컴파일러가 매번 같은 방식으로 실행되고 빌드 실패가 발생하면 실패한 작업만 다시 실행하여 재현하면 됩니다

 

둘째 더 효율적인 빌드가 가능합니다

어디에도 묵시적인 상태가 유지되지 않습니다.

즉 이제 결함이 없는 빌드가 모듈을 다시 빌드합니다

이제 빌드 시스템이 모듈 그래프에 대해 완전히 알아서 모듈이 빌드될 때까지 기다리며 컴파일 작업으로 실행 라인이 중단되지 않도록 정보에 기반한 더 나은 스케줄링 옵션을 만들 수 있습니다

 

마지막은 Xcode에서 디버깅할 때 디버거에 Swift 모듈을 전달한다는 것입니다

 

프로젝트가 묵시적으로 빌드된 모듈을 사용하여 구축될 경우 Xcode 빌드와 디버거에는 완전 별개 모듈 그래프가 포함됩니다.

 

명시적으로 빌드된 모듈을 사용해 디버거에서 이미 빌드된 모듈을 재사용할 수 있습니다

예를 들어 ‘p’ 또는 ‘po’를 사용한 표현을 평가하는 경우처럼 디버거가 Swift 유형에 대해 알아야 할 때 모듈을 다시 빌드하는 일을 피할 수 있습니다 

 

 

Module build log

명시적 빌드 모듈은 빌드 로그에 작업 표시 방법에도 영향을 미칩니다.

Xcode 16에서 명시적 빌드 모듈은 모든 C, Objective-C 코드에 사용됨

 

프로젝트선택후 Build Settings를 선택하고 필터 상자에 ‘explicitly built’를 입력

Explicitly Built Modules 설정을 선택해 ‘Yes’로 설정합니다

명시적으로 빌드된 모듈이 Clang과 Swift에서 활성화되었으니 빌드를 시작할 수 있음

 

첫 번째 작업은 빌드 로그에는 많은 스캔 작업이 포함됩니다 

프로젝트의 각 소스 파일에 대해 한 번 실행되며 빌드 시스템의 모듈 가져오기 그래프를 생성함

기본 제공 작업이며 새 프로세스를 생성하지 않습니다

이렇게 하면 빌드 시스템이 스캔되는 소스 파일 간의 정보를 캐싱할 수 있습니다

 

두 번째 새로운 작업은 컴파일 모듈 작업입니다 

이 작업은 특정 프로젝트나 타겟에 국한되어 있지 않습니다 

대신 이는 상위 수준의 작업이며 대상 간에 공유될 수 있습니다 

이 경우 빌드 시스템은 작업별로 각 컴파일러 프로세스를 생성함

이 작업은 특정 모듈을 컴파일된 모듈 파일로 빌드합니다

묵시적으로 빌드된 모듈의 정상적인 컴파일 도중에 발생했던 작업이며 이제 분리되었습니다

모듈을 만드는 동안 식별된 모든 진단이 여기에 첨부되며 어떤 소스 파일이 먼저 모듈을 빌드했는지에 영향을 받지 않음

 

 

 이 빌드에서는 UIKit 모듈이 여러 번 나타납니다 

그 이유는 일부 빌드 설정이 다른 대상에서 모듈의 다양한 변형을 빌드해야 할 수 있기 때문입니다 

이 빌드에는 2개의 Swift 모듈 변형과 4개의 Clang 모듈 변형이 있음 

 

두 개를 자세히 살펴보면 Xcode에 다른 해시가 표시됩니다 

이 해시는 이 모듈 변형을 빌드하는 데 필요한 명령줄 인수 모음을 나타냅니다 

이러한 플래그는 흔히 언어 표준 기능 매크로나 포함 경로의 차이 같은 것입니다 

자주 접하게 될 가능성이 있으며 묵시적 빌드 모듈에서도 발생했고 그러나 모듈 빌드가 빌드 시스템에 노출되지 않아 더 알아차리기 어려웠습니다 

 

컴파일러가 스캔 과정에서 이 인수 목록을 최적화하여 모듈 빌드에 영향을 미치지 않는 항목을 제거합니다

 

 

Optimize your build

추가 변형을 삭제해 보겠습니다 

먼저 프로젝트에서 만든 모듈의 추가 정보를 수집하고 싶습니다

 

클린빌드후 모든 모듈 다시 빌드해야함

Product > Perform Action > Build with Timing Summary를 통해 빌드 성능에 대한 추가정보를 수집가능함

 

여러 모듈 변형은 호환되지 않는 빌드 설정이 있는 다른 소스 파일로 인해 발생합니다 

예를 들어 C 소스 파일과 Objective-C 소스 파일은 일부 모듈을 매우 다르게 분석할 수 있습니다 

불필요한 모듈 변형은 빌드에서 수행해야 하는 추가 작업을 야기합니다

 

일부 일반적인 모듈 변형 소스는 

전처리기 매크로 추가,

언어 모드 추가(단일 C 파일이 Objective-C와 혼합된 경우),

언어 버전(Objective-C를 C17 등의 최신 C 버전과 함께 사용하는 경우),

Automatic Reference Counting 비활성화가 해당됩니다

 

다시 돌아와서 빌드가 완료되면

필터로 이동해 ‘modules report’를 입력합니다 

Clang 모듈 보고서를 선택하면 UIKit의 변형 두 개와 다른 많은 모듈이 나열됩니다

Swift 모듈 보고서를 선택하면 두 개의 변형이 표시됩니다 

여기에 나온 내용이 앞서 빌드 로그에서 확인했던 UIKit 네 가지 변형을 뒷받침합니다

 

변형 개수를 줄이려면 일반적으로 이 변형이 발생하는 데 영향을 주는 빌드 설정을 점검해야합니다.

프로젝트 탐색기에서 프로젝트를 선택하고 필터 상자에 ‘macros’를 입력해 추가 매크로 설정을 확인합니다

프로젝트 수준에서 확인되는 것이 없으므로 타겟 빌드 설정으로 이동합니다

타겟에는 다른 타겟에는 없는 ENABLE_FEATURE 매크로가 있습니다
‘Levels’ 옵션을 선택해 동시에 프로젝트 레벨 설정도 확인합니다

대상에서 이 매크로를 제거하고 프로젝트 설정에 배치합니다.

다시 ‘Product’ ‘Clean Build Folder’ ‘Build with Timing Summary’를 선택해서 빌드하면

 

이번에는 빌드 로그에 UIKit 변형 세 개만 표시합니다 

하나는 Objective-C용이고 두 개는 Swift용입니다 

하나는 Clang 모듈용이고 다른 하나는 Swift 모듈용입니다

 

프로젝트 설정을 통합하여 이러한 개별 그래프를 가져와 하나로 합칠 수 있습니다 

일반적으로 프로젝트에서 빌드 설정을 가능한 넓게 적용하는 것이 타당합니다

대상 수준에서 언어 표준을 설정하는 대신 프로젝트나 작업 공간 수준 이상으로 올려야 합니다 

그러면 모듈을 소스 파일 간에 가능한 한 많이 공유할 수 있습니다.

이것이 바로 명시적으로 빌드된 모듈입니다

반응형