[Swift] Mergeable Libraries, 병합된 라이브러리
안녕하세요
엑스코드가 발전됨에 따라
모듈화할때 예전과 달라지도록 만든 개념중 하나인 Mergeable Libraries에 대해 알아보려고합니다
Mergeable Libraries가 뭔가요?
간단하게 요약하면
debug 빌드에서 모듈들을 동적으로 연결하고
release 빌드에서 모듈들을 정적으로 연결한다
정도로 요약할 수 있을 것같아요
Xcode14를 사용했을 시절... 혹은 그 이전...
모듈화를 고려할때
동적으로 모듈을 만들지, 정적으로 만들지 고민해야하는데
동적보단 정적 모듈을 권장했어요
우선 둘의 차이를 알아보면
정적모듈
- 앱의 실행파일에 직접 복사되서 빌드시간이 느려짐
- launch time이 빠름
동적모듈
- 앱의 빌드시간이 빠름
- 런타임에 모듈들이 링킹되면서 launch time이 느림
이런차이로
보통 유저의 경험을 향상시키기위해 launch time을 좀더 우선시 고려했고
동적 모듈보단 정적 모듈 사용을 권장했죠
6개까지 동적모듈을 사용해라~ 라는 애플의 권장사항도 있듯이 동적 모듈을 최소한 사용하는게 좋다고 했었어요
하지만!
Xcode15부터 새롭게 생긴 개념으로
병합 가능한 동적 라이브러리를 사용하면
디버그 빌드에서 동적으로 연결된 빌드 시간을 잃지 않고
릴리스 빌드에서 정적 링크와 유사한 앱 실행 시간을 얻을 수 있습니다
동적모듈의 장점과 정적모듈의 장점 모두 얻을 수 있는 빌드시스템이 생겼습니다
동작방식을 요약하면
Debug일때 - 동적로드 방식
- mergeable library코드를 클라이언트 코드에 병합하지 않음
- 모듈이 암시적으로 re-export하는 것처럼 처리함
- Xcode는 MERGEABLE_LIBRARY가 활성화되어 있지만 MAKE_MERGEABLE이 아닌 타겟의 직접 종속성을 링커 플래그 -reexport_framework, -reexport-l 등을 사용하여 링크
re-export란?
@_exported import 키워드를 사용하면 import한 모듈의 심볼을 마치 자신의 인터페이스의 일부인거처럼 외부에 다시 노출할 수 있는 방법
사용시
-> 같은모듈에서 import 사용없이 접근가능
-> 해당 모듈을 import하면 @_exported import된 모듈에도 접근가능
Release일때 - 정적로드 방식
- mergeable library의 코드를 클라이언트 코드에 직접 병합함
- 라이브러리 코드가 앱속으로 병합되서 원래 라이브러리가 어디있엇는지 경계가 모호해짐
- Xcode는 링커 플래그 -merge_framework, -merge-l 등을 사용하여 MAKE_MERGEABLE이 활성화된 직접 종속성의 모든 제품을 병합
설정되지 않은 모듈인경우
Xcode는 병합 가능한 라이브러리가 활성화되지 않은 대상에 대해 일반 링크를 사용
Xcode가 정적 라이브러리 또는 병합할 수 없는 동적 라이브러리에 사용하는 것과 동일한 링크
동적과 정적의 장점을 모두 사용해서
개발할땐 빌드속도를 향상시키고
배포할땐 launch time을 향상시켜서
빌드유연성
성능최적화
이점을 모두 얻어간다고합니다!
테스트를 한번 해볼까요~?
Mergeable library를 사용하기위해선 동적모듈이여야합니다!
따라서 모든 모듈을 동적으로 구현했어요
Core모듈이 병합된 모듈이될거구요
Analytics, Network모듈을 병합가능한 모듈로 만들어볼게요
따라서 MergeablePrac모듈은 Core모듈만 의존하면 나머지 2개의 모듈에대한 정보를 알 수 있습니다.
병합가능한 모듈의 설정은
자동, 수동설정이가능한데
자동으로하는경우 다이나믹라이브러리를 찾을 수 없다는 에러가 뜨는 경우가 있으니
수동을 권장하고 있어요
병합가능한 모듈설정을위해 빌드셋팅에 설정해야할 옵션이 이렇게 2개가 있어요
Build Mergeable Library(MERGEABLE_LIBRARY) -> 병합시킬 라이브러리(하위모듈)에 적용
동적 라이브러리 및 프레임워크의 경우 이 대상의 바이너리를 병합 가능한 라이브러리로 링크하여 해당 대상에 종속된 대상의 제품에 병합하도록 구성한 경우 병합할 수 있습니다.
이 대상은 릴리스 빌드에서는 병합 가능한 라이브러리로 링크되어 병합할 수 있지만 디버그 빌드에서는 일반 동적 라이브러리로 링크되어 다시 익스포트됩니다. 다른 바이너리 유형에는 이 설정이 적용되지 않습니다.
Create Merged Binary(MERGED_BINARY_TYPE) -> 병합된 라이브러리(상위모듈)에 적용
이 설정을 사용하면 대상의 바이너리를 링크하는 병합 가능한 라이브러리와 결합하여 단일 바이너리를 생성하여 링크할 수 있습니다.
실행 파일, 동적 라이브러리 및 프레임워크에만 적용됩니다.
자동설정은 앱타겟에
BuildSetting -> Create Merged Binary옵션을 automatic으로 설정해주면 끝입니다
바이너리 의존 목록확인
otool -L {바이너리이름}
을 통해서 바이너리 파일 구조를 확인할 수 있어요
otool은 macOS에서 바이너리 파일의 다양한 정보를 추출하는 데 사용되는 도구라고 합니다
release일때 Create Merged Binary가 no일때 빌드하고 바이너리 동적라이브러리 목록을 출력해봤어요
Core와 하위모듈들이 모두 연결된걸 확인할 수 있죠
Create Merged Binary에 automatic를 적용하고 다시 빌드하면
라이브러리 목록은 아래와 같아요
저희는 Util을 병합시키고 싶지않았는데
자동으로 병합된 상태인걸 볼 수 있죠
수동으로 변경해서 다시 시도해볼게요
다시 초기화해서 최초상태를 한번 확인해보면
@rpath로 Analytics, Network, Util모듈이 잘 연결된것을 볼 수 있어요
수동설정은 Create Merged Binary옵션을 manual로 하고
병합될것들은 Build Mergeable Library = yes, 병합하지않고 디스크에 남겨야하는건 = no로 설정하면 됩니다
Analytics, Network 모듈을 병합해볼게요
이렇게 설정해주고
다시 빌드해서 확인하면
바이너리 파일에 동적 프레임워크로 포함되지 않는걸 볼 수 있어요
잘 병합된거겟죠?!
이때 추가적으로 해줘야할게있는데
병합된 경우 앱 실행파일안에 코드가 복사 됬기 때문에 export할 필요가없어요
하지만 병합된 라이브러리는 기본적으로 export심볼을 앱안에 그대로 남겨요
외부에 심볼을 내보낼 이유가없는데 심볼때문에 앱의크기가 커짐 -> 빌드시간 느려짐
이라는 문제가 발생하는데
Other Linker Flags에
-Wl,-no_exported_symbols
를 추가해서 심볼을 제거할 수 있어요
이때 필요한 심볼도 제거될 수 있는데
따로 필요한 심볼은
General > Linking > Exported Symbols File에서 설정해주면 되요
심볼이 잘 제거됬는지 어떻게확인하냐면
nm을 이용해서 합니다
심볼 테이블 출력
nm -gU {바이너리 이름}
이렇게 Analytics관련된 심볼이 있었다가
Other Linker Flags 설정을 해주면
아래같이
심볼목록에서 제거된 모습을 볼 수 있습니다!!
네트워크 모듈도 마찬가지로 적용해주면
심볼이 깔끔하게 다정리된 것을 볼 수 있어요
Analytics, Network 코드가 Core에 병합됬기 때문에
MergeablePrac이 이제 Core만 참조하도록 하면 됩니다!
똑같은 설정으로 Debug모드를 실행하면
다시 동적프레임워크들로 잘 연결 된것을 볼 수 있구요(심볼은 그대로 제거상태입니다 Other Linker Flag와 별도 동작)
실행도 해볼건데 이때
병합된 프레임워크를 사용했고 심볼도 제거한 경우 주의해야합니다
저흰 위에서 Network모듈을 병합했고 심볼도 제거했었죠?
상위모듈에서 해당 심볼을 찾을수 없어서
HostURL을 사용하려고할 때 찾을수 없다고 이렇게 아래와같은 에러가 발생합니다
이때 해결방법은
1. 직접의존하기
이런식으로 직접 의존해서 쓰는방법이있는데..
이러면 모듈간의 의존성 관계가 애매모호해 질것같죠?
2. 위에서 말한 Exported Symbols File설정
노출이 필요한 특정한 심볼만 골라서 설정하는 방법도있어요
3. 디버그, 릴리즈모드에 맞게 other linker flag적용
이 방법이 제일 편하고 안전할 것 같단 생각이드네요!
Xcode15+로 개발하는 개발자분들은
애플이 정적 -> 동적 프레임워크로 변환하고 병합을 사용하는것을 권장하라고 말하는만큼
동적 프레임워크 + Mergeable적용을 지향하는 방향으로 모듈화를 고려하면 좋을것 같네요!