[iOS] 빌드 결과로 보는 Static Framework 와 Dynamic Framework

이번에는 이해할 수 있을까요? 🤔

naljin
15 min readOct 13, 2022

개-하! 오랜만에 돌아왔읍니다

요즘은 프레임워크 설정을 조져볼 기회들이 있어서 이것 저것 테스트해보고 있는데요, 그러면서 깨닫게 된 것들이 많아 간략하게 공유해보려고 합니다. 더 복잡한 케이스는.. 사내 블로그에..? 🤔

어쨌든! 한번쯤은 들어보셨죠? 스태틱.. 다이나믹 프레임워크..

후.. 한번 알아봐야지 하고 찾아보면 뭐 static 은 라이브러리 코드가 앱 실행 파일에 직접 복사되고, dynamic 은 라이브러리 코드가 별도의 executable binary 로 생성되고 앱 실행 파일에서는 참조만 갖고 있는다.. 뭐가 느리고 빠르고 어쩌구 저쩌구…

👀 : 읽을게

🧠 : 뱉을게

완벽한 눈과 뇌의 비협조 콜라보레이션~!

ㅋㅎ 저만 그런거 아니었겠져? 그럼 이제 어르고 잘 달래서 이해시키러 가봅시다 ㄱㄱㄱ

Framework 생성 및 사용

뭐 스태틱 프레임워크는 뭐다, 다이나믹 프레임워크는 뭐다 이런 이해 안되는 설명은 다 때려치고! 일단 냅다 빌드해볼건데요, 그러려면 일단 프레임워크 만들어야겠져?

안에 코드로는 대충 뭐 add 기능이 있는 계산기 구조체를 만들어보져

그리고 미리 생성해둔 NaljinApp 프로젝트에서 MyFramework 를 사용하기 위해 추가해줍니다.

General 의 Frameworks, Libraries, and Embedded Content 쪽에서도 + 버튼을 통해 MyFramework 를 추가해줄게요

마지막으로 NaljinApp 쪽에서 import MyFramework 를 통해 Calculator 의 기능을 사용하고 빌드하면?

굿 잘나옵니다

준비 완료!

Static Framework

이제 테스트를 위한 모든 준비는 다 끝났으니 MyFramework 를 Static 으로 설정했을때 빌드 결과가 어떻게 나오는지 볼게요

기본적으로 Framework 를 만들면 Mach-O Type 이 Dynamic Library 로 되어있는데여

우리가 지금 테스트할건 Static 이니까 Static Library 로 바꿔줍니다

이제 빌드를 한번 때리고 결과로 생성된 app 파일을 찾아가야겠져? Products > NaljinApp 우클릭 > Show in Finder 로 쉽게 찾아갈 수 있습니다

여기서 패키지 내용보기를 통해 들어가면?

두둥 우리의 메인 앱 실행파일이 나옵니다

file 이란 명령어를 통해 지정된 파일의 종류(타입)을 확인할 수 있는데요, executable 이라고 나오네욤

 : 애초에 너가 Mach-O Type 설정을 Executable 로 해놨잖아..

🤔 : 흠.. NaljinApp executable 이 최종 실행 파일로 나온단 말이지.. 사실 NaljinApp 프로젝트에서 있던 ContentView.swift 의 코드들은 저 실행 파일에 들어가 있을 것 같긴해.. 그렇담 MyFramework 에서 작성했던 Calculator.swift 의 코드들은 어디 들어가있는데???

🙋🏻‍♀️ : 우리는 MyFramework 를 Static 으로 빌드했져. 그래서 NaljinApp 을 빌드할 때 MyFramework 코드도 다 포함을 해서 최종적인 NaljinApp executable 을 만들게 돼요!

그러니까 Static framework 의 특징을 말할때, “최종적인 app 의 크기가 커진다” 라는 말이 나오는겁니다. (사실 static 이라고 해서 모든 코드가 다 포함되는건 아니고, 필요한 부분만 선택적으로 로드한다지만… 어쨌든 커지는건 커지는거잖아요?)

🤔 : 진짜 진짜루?? NaljinApp executable 이 MyFramework 코드도 다 포함을 하는걸 어떻게 확인할 수 있는데??? 너가.. 멀 알어..!

🙋🏻‍♀️ : nm 명령어를 사용해보자구여..! nm 명령어를 통해 지정된 파일(오브젝트 파일, 실행 파일 또는 오브젝트 파일 라이브러리)의 symbol 에 대한 정보를 표시할 수 있습니다.

nm -debug-syms NaljinApp | grep "\.o"

위 명령어의 결과로 NaljinApp 프로젝트에 포함된 ContentView 나 NaljinAppApp 파일에 대한 object file 외에도, MyFramework 에 있던 Calculator 목적 파일도 같이 적재된것을 확인 할 수 있습니다.

🤔 : 홀리 몰리~~~ 근데 있잖아 아까 NaljinApp executable 이랑 같은 depth 에 Frameworks 라는 폴더가 있었거든? 여기에 다시MyFramework.framework 가 있더라구~?

이 안에 MyFramework 를 file 명령어로 확인해보면 정적 라이브러리인걸 알 수 있었어 ㅇㅇ

근데 이미 MyFramework 에 필요한 코드는 다 NaljinApp 에 들어가있는 상태잖아?? 그럼 얘가 왜 필요한거야??

🙋🏻‍♀️ : 맞아여. 그래서 보통 object file 들로만 구성된 static library 의 경우 Frameworks 폴더 하위에 있을 필요가 없어요! 필요한 object file 들은 이미 NaljinApp 실행 파일에 로드되어있으니까, 사실 저 폴더는 일종의 중복된 리소스인 셈이져

🤔 : 그럼 저 폴더를 어떻게 없애냐!

🙋🏻‍♀️ : 아까 Xcode 의 General 탭에서 Frameworks, Libraries, and Embedded Content 에 MyFramework.framework 를 추가했던거 기억나나여? 이때 기본적으로 Embed & Sign 옵션으로 선택 되어있는데, 이 값을 Do not Embed 로 바꿔줍니다.

그리고 빌드해보면 Frameworks 폴더가 없어졌습니다.

Do Not Embed는 말 그대로 “이 프레임워크의 모든 콘텐츠를 main application 과 함께 포장하지 말아라!” 라는 뜻이니까요. 즉, 최종 application package 가 Frameworks 폴더 안에 해당 프레임워크 코드를 포함하지 않습니다.

Dynamic Framework

여기까지 오셨군요..앞에서 필요한 개념들은 대강 다 나왔으니 얘는 쩜 수월하지 않을까요..? ㅎ 그럼 가봅시다

일단 MyFramework 의 mach-o type 을 Dynamic 으로 바꿔야겠져

그리고 Embed 옵션은 Embed & Sign 을 해줄거예여. 왜인지는 조금 이따가 다시 살펴보기로하고 일단 돌려봅시다 ㄱㄱ

폴더 구조를 살펴보면 요렇게 되어있는데요, 다이나믹 라이브러리의 output 이 별도의 executable binary 로 생성된 것을 확인할 수 있습니다.

이번에도 nm 명령어를 통해 NaljinApp 의 symbol 에 대한 정보부터 뽑아보져

nm -debug-syms NaljinApp | grep "\.o"

그 결과 NaljinApp 프로젝트에 포함된 ContentView 나 NaljinAppApp 파일에 대한 object file 은 포함이 되어있지만, 아까와 달리 MyFramework 에 있던 Calculator 목적 파일은 적재 되어 있지 않은 것을 확인 할 수 있습니다. (라이브러리에서 최종 프로그램으로 코드를 복사하는 대신 동적 라이브러리에서 사용되는 심볼 및 런타임시 라이브러리의 경로를 기록해 두는 식이져.)

그럼 Calculator.o 를 어딘가에서 불러서 쓴다는 말일텐데,,! 여기서 otool 명령어를 이용해 NaljinApp 이 사용하고 있는 사용하고 있는 shared library 를 출력해봅시다.

otool -L NaljinApp

@rpath/MyFramework.framework/MyFramework 가 있네여!

여기서 @rpath 는 Runpath Search Path 를 지칭하는 환경 변수로, Xcode 에서는 기본적으로 @executable_path/Frameworks 가 설정되어있습니다.

@executable_path 는 프로세스의 메인 실행파일을 포함하고 있는 디렉터리 경로를 뜻하니까, 결국 NaljinApp/Frameworks/MyFramework.framework/MyFramework 를 레퍼런스 하고 있는거겠죠?

이게 바로 dynamic framework 를 embed 할때 옵션을 Do Not Embed 가 아닌, Embed & Sign 으로 설정해줘야하는 이유입니다.

만약 Do Not Embed 로 Dynamic framework 를 사용하면 어떤 일이 발생할까요?

NaljinApp executable 은 Calculator 의 코드를 사용하지만, Calculator.o 파일은 없는 상태입니다. 이렇게 앱 내부에 없는 외부 심볼을 확인하기 위해 shared library 를 살펴보게 되는데, 이때 NaljinApp/Frameworks/MyFramework.framework/MyFramework 는 존재하지 않습니다. 왜냐면 아까 Static framework 에서 살펴봤듯이 Do Not Embed 로 프레임워크를 가져오면 NaljinApp 패키지 내부에 Frameworks 가 포함되지 않기 때문이죠. 결국엔 dyld: Library not loaded 라는 동적 링커 쪽 에러가 발생합니다.

참고로 dyld 는 동적 라이브러리를 로드하기 위한 것으로, 메인 실행 파일에서 시작합니다. Mach-o 를 파싱하여 종속 dylib, 즉 필요한 동적 라이브러리를 찾습니다. 동적 라이브러리를 사용하게 되면 앱 시작시 프로그램 파일 하나만 로드하는 것이 아니라, 모든 dylib 들이 로드되고 함께 연결되어야합니다. 따라서 앱의 시작 속도가 느려집니다. (이제 dynamic framework 를 설명하는 글에서 앱의 시작 속도가 느려짐 어쩌구.. 하는 말이 이해가 되시나요!?)

마지막으로 Frameworks 안에 있는 MyFramework 는 file 명령어로 확인해봤을때 dynamic library 고, nm 명령어로 확인해봤을때 Calculator.o 가 포함되어있는걸 알 수 있습니당

번외

위에서는 헷갈릴까봐 적지 않았지만 Static framework 여도 media bundle 에 접근할 경우에는 Embed & Sign 을 사용한다고도 해여..

그래서 MyFramework 안에 이미지 넣구

Embed & Sign 으로 설정한 뒤

NaljinApp 프로젝트의 ContentView.swift 에서는 아래와 같이 불러왔거든여?

근데 이미지는 안불러와지고 콘솔에는 이런 로그가 뜨네염

No image named 'naljin' found in asset catalog for /Users/sujikang/Library/Developer/CoreSimulator/Devices/8A20DAF2-F337-41BB-89EB-9F1514C2053B/data/Containers/Bundle/Application/9EEC9675-05BA-43DC-A799-B94740C981C4/NaljinApp.app

흠…… 프레임워크를 다이나믹으로 바꾸기만 하면 이미지 잘 불러와지던데…….. 뭐지………이 상황을 말하는게 아니었나..? 싶네여..? 아시는 분은 제발 알려주세여

→ 아래 글에서 해당 문제를 다루고 있는거 같은데 일단 레퍼런스만 남겨두고 스킵

If you want to build your module as a static library, you can catch a conflict that will say: “I have the Assets.car file from your module and the same file from the application. I don’t know what to do…” Until resolving the conflict, you won’t be able to build your app

마무리

이제서야 족굼씩 Static , Dynamic Framework 를 알아가는 느낌,,^^? ㅎㅎ

아무튼 이번에 앱 파일 까보면서 재밌었고, 역시나 직접 해봐야 좀 이해가 되는구나…^^ 를 느꼈습니다 ㅎ

이번 글도 누군가에게 도움이 되길 바라며,,! 틀린 내용 있으면 알려주세여!

그럼 20000!

참고

http://egloos.zum.com/dstein/v/2867550

https://zeddios.tistory.com/1308

--

--