모듈화를 진행하다 보면 Static Linking을 사용할 것인가? Dynamic Linking을 사용할 것인가? 에 대한 끊임없는 고민을 하게 된다.
모든 상황에 완벽한 하나의 정답은 없고 모든 것에는 Trade-off가 존재한다.
App Launching Time, Build Time, App Size 등을 고려해야 하는데 Expedia 팀에서 이를 잘 실험한 글이 있어 요약해보려 한다.
특히 여기서 실험한 방식들을 엿보면, 최근 골머리를 앓고 있는 Static Framework 복사 조건에 대해서도 어느정도 힌트를 얻을 수 있는 것 같다.
https://medium.com/expedia-group-tech/the-effects-of-linking-dependencies-on-app-performance-and-development-8162e5610f75
가설
- 대부분의 Dependency가 Static linking으로 이루어진 경우, Launcing Time이 더 빠르지만 App Size가 커진다.
- 대부분의 Dependency가 Dynamic liking으로 이루어진 경우, Launcing Time이 더 느린 대신 App Size는 작게 유지된다.
'Static Linking을 하면 App Size가 커진다.' 라는 잘 알려진 명제가 최근 나를 좀 괴롭게 한다.
많은 자체 실험 결과 모든 의존성이 Static Framework(또는 Library)로 이루어진 경우 정적 링커가 중복되지 않도록 처리해주는 것으로 보이는데, 왜 많은 사람들이 공유 라이브러리에 대해 Dynamic Framework를 사용하라고 하는 것인가?
'이는 App Extension이나 다른 Widget에서 사용되는 경우, 또는 여러 Dynamic Framework에 의존성이 중복으로 걸려 있는 경우 복사될 수 있다.' 라고 생각이 정리 되는데, 해당 아티클을 읽고 Static Linking만 존재하는 경우 dead code stripping으로 인해 중복 문제를 걱정하지 않아도 된다는 점을 유추할 수 있다.
실험
- App
- Application Target
- Dependency : Module1, Module2, Alamofire, ApolloGraphQL, Facebook
- UITest target
- Dependency : Module1, Module2, Alamofire, ApolloGraphQL, Facebook
- Application Target
- Module1
- Module1 Target
- Dependency: Core, Alamofire, ApolloGraphQL, Facebook
- Module1 Target
- Module2
- Module2 Target
- Dependency: Core, Alamofire, ApolloGraphQL, Facebook
- Module2 Target
- Core
- Core Target
- Dependency: X
- Core Target
- 모든 실험 결과 값들은 5회 실행 평균이다.
- App Launch Time은 이 UITest로 측정되었다.
- 클린 빌드를 위해 Xcode의 Product > Clean Build Folder 작업을 수행하였다.
- 증분 빌드(Re-build)의 경우 Module1의 뷰 컨트롤러를 미세하게 수정하였다.
여기서부터 가독성을 위해 Module1, Module2, Core를 내부 디펜던시,
Alamofire, ApolloGraphQL, Facebook를 외부 디펜던시라고 명시하겠다.
그리고 Dynamic Library로 Mach-O 타입을 설정했다는 것은, 일반적인 Dynamic Linking을 한다는 것이고 (app target에 embed)
Static Library로 설정한 것은, 일반적인 Static Linking을 한다는 것이다. (linking만)
또한 그 반대도 동일하다.
SpikeMixedLinking
- 내부 디펜던시의 Mach-O 타입 = Dynamic Library
- 외부 디펜던시는 Static Libary를 생성하는 SPM으로 가져온다.
- Core 프로젝트 내의 CoreDependencies라는 Umbrella Framework에 외부 디펜던시를 Static Linking을 한다.
- CoreDependencies는 외부 디펜던시가 필요한 곳에 Dynamic Linking을 한다.
- 이는 Duplicated Symbol을 방지하기 위함이다.
- 이 접근 방식은 실제 iOS Vrbo 앱이 설정되는 방식과 가장 유사하다.
SpikeStaticLinkingWithFrameworks
- 내부 디펜던시의 Mach-O 타입 = Static Library
- Static Library에서 리소스를 가질 수 없으므로 resource bundle을 따로 두고 App target에 복사한다.
- 외부 디펜던시는 Static Libary를 생성하는 SPM으로 가져오고 의존하는 타겟에 모두 Static Linking한다.
SpikeStaticLinkingWithLibraries
- 위 SpikeStaticLinkingWithFrameworks 와 동일하지만, framework 대신 Static Libary로 생성
SpikeStaticLinkingWithSwiftPackages
- 내부 디펜던시를 모두 local swift package로 생성 (Static Library)
- 외부 디펜던시는 remote swift packages로 받아와서 의존하는 곳에 Static Linking
SpikeDynamicLinkingWithXCFrameworks
- 내부 디펜던시의 Mach-o 타입 = Dynamic Library
- 외부 디펜던시는 Carthage에서 XCFrameworks로 생성하고 의존하는 타겟에 Dynamic Linking. 단 Facebook은 이를 지원하지 않아 SpikeMixedLiking에서 서술한 Umbrella Framework 방식을 사용.
결과
App Size
Static SPM < Static Library = Static Framework < Mixed < Dynamic with XCFrameworks
- Static Linking으로 이루어진 경우 App Size가 낮게 나타나고, Dynmaic Linking with XCFrameworks가 가장 크다.
- App Size(.executable)을 보면 반대의 결과이지만, Static Linking은 바이너리가 앱 Executable에 바로 포함되기 때문에 당연한 결과이다.
- Dynamic Linking 시에는 전체 바이너리가 포함되어야 하고 정적 링커는 dead code를 strip을 할 수 있다. (DEAD_CODE_STRIPPING)
- 일반적으로 사람들이 간과하는 내용과 다른 부분
App Launch Time
Static SPM < Static Framework < Static Library < Mixed < Dynamic with XCFrameworks
- Static Linking으로 이루어진 경우 App Launch Time이 작고, Dynamic with XCFrameworks가 가장 크다.
- 위 순위의 기준은 Device 기준이다.
- 시뮬레이터에서는 예상처럼 동작하지 않는데 크게 중요하지 않을 것 같다.
Build Times
Dynamic with XCFrameworks
- Clean Build Time에 있어서 Dynamic with XCFrameworks가 가장 빠르다.
- Rebuild 타임에는 Static SPM이 우위를 보인다.
Build Dependencies
- SPM과 Carthage의 비교에 의미가 있으며 SPM이 더욱 빠르다는 것을 알 수 있다.
결론
- Swift packages를 이용한 Static Linking이 전반적으로 가장 좋은 퍼포먼스를 보인다. (clean build time은 느리다.)
- 모든 의존성 그래프를 Static으로 유지하는 것은 어려운 일이다.
- 특히 앱의 일부 디펜던시가 자체 repo를 가지며 앱이나 xcworkspace 내의 다른 디펜던시와 동일한 디펜던시를 공유하는 경우에 더욱 어렵다. (공유 디펜던시의 버전이 다르거나 그런 문제인듯?)
- 아니면 소스코드를 소비자에게 직접 제공하지 않고 binary framework로 주고 싶을 수도 있다.
- 위와 같은 내용들은 퍼포먼스와 관련 없이 디펜던시 관리 방식을 제한시킨다.
- 최소한 이런 결과를 통해 Dynamic Linking보다 Static Linking을 수용하는 접근 방식을 취할 수 있다.
'iOS > Module' 카테고리의 다른 글
Tuist - tuist test (0) | 2024.11.07 |
---|---|
Tuist - 기존 프로젝트에 Tuist 도입 배경 및 효과 (7) | 2024.08.18 |
Static Framework의 번들/리소스 (0) | 2024.04.21 |