프로젝트 요구사항이 추가되며 전체 규모가 점점 커짐에 따라 익스텐션 타깃의 수 또한 자연스럽게 증가하였다. 이 과정에서 메인 앱과 익스텐션 간에 중복되는 코드가 다수 존재하였고, 이를 개선하기 위해 공통 로직을 별도의 프레임워크로 분리하여 모듈화 작업을 진행하였다. 그 결과, 메인 앱과 익스텐션은 해당 프레임워크를 통해 공통 코드를 참조하는 구조로, 코드 재사용성을 향상할 수 있었다.
프레임워크의 추가는 아래의 단계와 같이 간단하게 추가할 수 있었다.
File > New > Target > Framework를 선택한다.

프레임워크 이름과 각종 설정을 입력한다.
이때, Project 항목에도 호스트 앱을 지정하여 타깃을 설정하고, Embed in Application 옵션에도 호스트 앱을 설정하여 호스트 앱 번들 안에 프레임워크가 포함되도록 한다.

위의 과정까지 마치면, 왼쪽 탭에서 프레임워크 파일이 추가되고, 호스트 앱의 Frameworks 설정에 추가한 프레임워크가 잘 등록된 것을 확인할 수 있었다.

프레임워크 타깃 > Build Settings > Linking > Mach-O Type을 확인해 보면, 여러가지 링크 옵션이 나타나는데, 여기서 Dynamic Library와 Static Library에 대해서 알아보자.

참고로, 메인 앱 타깃의 경우 실행 파일 형태인 Executable로 설정 된다.
프레임워크를 새롭게 추가하면 기본적으로 설정되는 옵션으로, 런타임에 동적으로 프레임워크가 링크 됨을 의미한다. 다시 말해, Framework 앱 실행 파일에 TestFramework의 목적 파일을 포함하지 않고 런타임에 사용할 프레임워크의 심볼 및 경로만을 가지고 있는 형태이다. 따라서 빌드 시간을 단축할 수 있지만, 런타임 실행 시간은 늘어나게 된다.
Dynamic Library는 빌드 시 메인 앱의 실행 파일에 포함되지 않기 때문에, 반드시 런타임에 동적으로 참조할 수 있도록 메인 앱 타깃에서 Framework 추가시, Enbed & Sign 옵션을 선택한다.

이때, 런타임에서 참조할 프레임워크의 경로는 @executable_path(메인 앱) 디렉토리 아래 Frameworks 디렉토리를 참고하게 된다. 해당 경로는 Xcode의 Build Settings의 Runpath Search Paths를 통해서 설정할 수 있었다.

참고로 Runpath Search Paths에서 설정한 대로, 앱 번들 내부에 Frameworks라는 디렉토리가 존재 했으며, 해당 디렉토리 내부에 TestFramework라는 프레임워크 실행 파일이 포함되어 있었다. 이를 통해 해당 프레임워크는 빌드 시 실행 파일에 병합되지 않고, 런타임에 동적으로 로드되어 참조되는 구조임을 확인할 수 있었다.


Static Library는 빌드 시 메인 앱과 정적으로 링크되는 형태를 의미한다. 즉, FrameworkTest 앱을 빌드할 때, TestFramework의 코드가 실행 파일에 직접 포함되어 최종적으로 하나의 Executable로 생성된다. 따라서 Static Library는 앱 크기의 증가와 빌드 시간이 증가할 수 있지만, 런타임 시간을 단축할 수 있다.
Static Library는 이미 메인 앱 내부에 포함되어 있기 때문에, 앱 번들 내 Frameworks 디렉터리에 동일한 프레임워크 파일이 존재할 경우, 이는 중복된 코드가 포함된 구조로 에러를 발생시킬 수 있다. 이를 해결하기 위해서는 메인 앱 타깃의 Frameworks, Libraries, and Embedded Content 설정에서 해당 프레임워크의 Embed 옵션을 Do Not Embed로 지정해야 한다. 이렇게 하면 정적으로 링크된 Static Library가 앱 번들 내 Frameworks 디렉토리에 중복으로 포함되는 것을 방지할 수 있다.

새롭게 프로젝트를 빌드 후, 아래와 같이 앱 번들 내부에 Frameworks 디렉토리가 없어진 것을 확인할 수 있었다.

처음 모듈화 과정을 마치고, App Store Connect를 통해 앱을 업로드 했을 때, 업로드 에러와 함께 메일로 아래와 같은 이슈를 전달 받을 수 있었다.
ITMS-90205: Invalid Bundle - The bundle at ‘XXX.appex/Frameworks/XXX.framework’ contains disallowed nested bundles.
ITMS-90206: Invalid Bundle - The bundle at ‘XXX.appex/Frameworks/XXX.framework’ contains disallowed file ‘Frameworks’.
App Store Connect 정책상, 앱 익스텐션 내부에서 Dynamic Framework 번들을 임베드하는 것은 제한되어 있다.
따라서 앱 익스텐션은 호스트 앱에 종속되도록 설계되어 있기 때문에, 이를 반영하여 호스트 앱에서만 Dynamic Framework를 Embed & Sign하고, 앱 익스텐션에서는 Do Not Embed로 수정하였다.