본문 바로가기

iOS

(iOS) Link fast: Improve build and launch times - WWDC22 앞부분 정리

프로젝트를 개발하는 과정에서 코드를 수정하고 빌드를 할 때 단순히 Build(Command + B) 버튼을 누르고 기다리기만 하는 경우가 많다. 

Link fast: Improve build and launch times에서는 빌드 과정에서 일어나는 일 중 특히 컴파일 및 링킹 과정이 어떻게 수행되는지를 잘 설명해주고 있다. 사내 세션 준비도 할 겸 해서 정리해 봤는데 포스팅까지 하기가 오래 걸렸다. 그럼 시작해 보자

 

Static Library


처음 프로그램을 개발할 때는 기능이 많지 않고 소스코드의 양도 많지 않았다. 하나의 소스파일을 컴파일하면 하나의 실행 프로그램이 나왔다.

갈수록 프로그램에서 요구하는 기능이 커지게 되고, 단순 소스코드의 양뿐만 아니라 관리점의 분리가 필요함에 따라 여러 소스코드로 나누어서 개발할 필요가 생겼는데... 그럼 여러 소스 파일을 컴파일해야 하는 상황이 발생했다.

그렇게 각각의 소스파일을 컴파일하여 재배치 가능한 오브젝트 파일로 만들고 이렇게 생성된 오브젝트파일을 연결하여 하나의 실행 가능한 프로그램으로 만들어주는 링커가 등장했다.

그렇게 갈수록 오브젝트파일도 많아지고, 관리하기가 어려워지기도 했지만, 이렇게 작성한 코드들을 공유하고자 하는 사람들도 생겨나기 시작했다. 

그러기 위해선 좀 더 공유하기 쉬운 형태로 파일을 관리해야 했고 여러 오브젝트 파일을 하나로 관리해야겠다는 판단이 생겼다.

그렇게 ar이라는 툴을 이용해서 여러 오브젝트파일을 압축한 아카이브 파일이 생겼으며 이를 스테틱라이브러리라고 부르기 시작했다.

 

그럼 이렇게 작성된 코드가 프로그램이 되기까지의 과정은 어떻게 될지 한번 살펴보자

위와 같이 4개의 소스파일이 있다고 해보자. 

1. 이제 막 입사한 에버는 회사(main)가 시키는 일을 수행해야 한다.

2. 에버는 입사한 지 얼마 안 되어 업무 매뉴얼을 제대로 숙지하지 못했고, 아직 어려움이 많아 샐리에게 도움을 요청한다.

3. 오래전에 입사에서 일을 잘하고 있는 샐리는 에버를 도와주면서도 본인의 업무(unused)도 동시에 수행할 수 있다.

4. 같은 팀의 닉은 새로운 업무를 수행해야 하지만, 아직 기획서가 나오지 않아 대기하고 있다.

 

위의 4 단계에 대한 링킹 과정을 살펴보자.

위와 같이 4개의 컴파일된 오브젝트 파일이 존재한다.

각각의 파일에는 회색으로 해결(Resolved)된 회색 심벌과 해결되지 않은(Undefined) 심벌이 존재한다.

샐리와 닉은 먼저 입사했으니 Static Library로 묶어서 관리하고, 이제 막 입사한 에버는 그대로 둔다.

링킹 과정에서는 심볼 테이블을 통해 정의되어있지 않은 심벌을 찾고 주소를 할당하여 하나의 프로그램으로 만든다.

각각의 오브젝트파일이 커맨드라인에 전달된 순서로 링킹이 이루어진다.

가장 첫 번째 파일인 main.o파일을 보면 main에 대한 심벌은 해결되었으나, ever에 대한 심벌은 정의되어있지 않다. 

다음으로 ever.o파일을 로드하여 ever에 대한 심벌이 해결됐고, sally에 대한 심벌이 정의되어있지 않다.

다음으로 라이브러리로부터, sally.o 파일을 로드하여 모든 심볼이 해결되었고, 사실 불필요했던(호출되지 않을) unused에 대한 심벌도 함께 추가됐다.

이제 모든 심벌이 해결되었으므로 각 심벌에 대한 주소를 할당한다.

더 이상 해결할 심벌이 없기 때문에 nick.o는 로드되지 않고, Home이라는 실행 가능한 프로그램이 생성된다.

 

Dynamic Library

 


앞서 여러 오브젝트 및 스테틱 라이브러리를 링커를 통해 하나의 실행가능한 프로그램으로 만든다고 했다. 문제는 이러한 라이브러리들이 늘어날수록 복사되는 코드의 양도 많아진다는 것이다.

그러면 오른쪽 프로그램과 같이 크기가 매우 증가하게 되는 문제를 해결하기 위해서는 어떻게 해야 할까?

이를 위해 다이나믹 라이브러리가 등장했다. 스테틱 링킹 과정에서는 실행 파일에 필요한 코드를 모두 복사하는 반면, 다이나믹 라이브러리는 해당 코드만 별도로 먼저 링킹 과정을 통해 실행 가능한 파일(Executable File)로 만든다. 

 

최종 실행 파일과 다이나믹 라이브러리 모두 실행파일인데 차이점이 무엇인가... 하면 우리가 일반적으로 실행하는 앱(Application)은 EntryPoint라는 실행 지점이 있다. 일반적으로 main함수를 EntryPoint로 두고 여기에서 실행 시 필요한 초기화 작업 등을 처리한다.

 

반면 Dynamic Library는 실행 가능한 파일이기는 하나, EntryPoint 없이 단순히 메모리에만 상주한다. 

메모리에 상주하고 있는 다이나믹 라이브러리는 여러 프로그램에서 공통으로 참조하여 필요한 기능을 사용할 수 있도록 한다.

스테틱 링킹에서는 코드가 모두 복사되지만, 다이나믹 라이브러리는 해당 코드를 사용할 것이라는 일종의 약속을 정해두고, 런타임에 해당 라이브러리를 Dynamic Linker를 통해 로드하여 사용한다.

 

이렇게 하면 모든 프로그램이 동일한 코드를 공유하여 사용할 수 있으므로, 전체적인 프로그램 크기가 감소하게 된다. (대신 프로그램 실행 시 해당 동적 라이브러리가 존재하지 않는다면, 런타임에러로 프로그램이 종료된다.)

 

모바일에서는...


위에서 설명한 다이나믹 라이브러리의 장점은 (일반적인) 모바일 앱이 아닌 보편적인 프로그램에서의 장점이라고 할 수 있다. 우리가 iOS애플리케이션을 개발할 때는 필요한(직접 개발하거나, Third Party의) 라이브러리를 동적으로 로드해서 사용하는 상황이라면, 이 동적라이브러리를 다른 앱과 공유해서 사용하는 것이 아닌, App Bundle에 Embed 해서 사용한다는 것이다. (최종적으로 배포되는 앱의 크기는 동적라이브러리를 쓴다고 해서 줄어들지 않는다.)

 

대신 빌드 관점에서는 장점을 찾을 수 있는데, 동적 라이브러리를 의존하는 앱에서는 해당 라이브러리의 심벌이 변경되지 않고 기능만 변경되는 경우 해당 동적 라이브러리만 재빌드 하고 앱은 다시 빌드할 필요가 없어 전체적인 빌드 시간을 단축시켜 준다. 따라사 Static, Dynamic을 필요에 따라 적절히 분배해 프로젝트를 구성하는 것이 좋다.

 

다만, UIKit, Foundation과 같은 프레임워크들은 동적으로 로드되기는 하나, OS에서 관리되는 시스템 라이브러리이기 때문에, 일반적으로 개발되고 배포되는 앱에서 모두 공유하여 사용한다는 장점을 잘 살린 것이다. 


다음에는 이러한 동적, 정적 라이브러리(프레임워크)를 어떻게 구성해야 효율적으로 빌드할 수 있는지 살펴보자.