본문 바로가기

iOS/Module

Static Linking vs Dynamic Linking

 
 
모듈화를 진행하다 보면 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

The Effects of Linking Dependencies on App Performance and Development

Can statically linked iOS Apps be both faster and smaller?

medium.com

 
 
 

가설

  • 대부분의 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
  •  Module1
    • Module1 Target 
      • Dependency: Core, Alamofire, ApolloGraphQL, Facebook
  • Module2
    • Module2 Target
      • Dependency: Core, Alamofire, ApolloGraphQL, Facebook
  • Core
    • Core Target
      • Dependency: X
  • 모든 실험 결과 값들은 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