Bitcode in iOS

오늘은 iOS의 비트코드(Bitcode)에 대해 알아보려 한다. 비트코드를 알아보게 된 계기는 현재 인턴을 진행하면서 기존 네이버 지도 API를 네이버 지도 V3 API로 교체를 해야 하는 작업을 과제로 받았다.

이를 위해 데모 프로젝트와 문서를 보면서 샘플 프로젝트를 만드려는데 시뮬레이터에서는 정산적으로 빌드가 되고 실행이 되는 반면 실제 디바이스에서 테스트를 진행하려니 비트코드 에러가 발생하면서 빌드조차 되지 않았다.

이 에러를 해결 방법은 간단했는데 Build Setting 탭으로 들어가 Enable Bitcode 항목을 No로 지정해주면 되었다. 하지만 늘 그렇듯 어떤 문제를 했는데 그 해결 방법이 어떠한 방법으로 문제를 해결했는지에 대해 알지 못하는 것만큼 불안한 것은 없다. 특히 이렇게 빌드 세팅의 속성을 변경해주는 경우에는 더 그렇다.

그래서 이에 관해 조금 찾아보았고 그 정보들을 정리해서 기록해보려 한다.

Bitcode

먼저 애플 공식 문서는 비트코드를 다음과 같이 설명하고 있다.

비트코드는 컴파일된 프로그램의 중간 표현(intermediate representation)이다. 비트코드를 포함한 앱을 앱 스토어에 업로드하면 앱은 컴파일되고 앱 스토어어와 링크될 것이다. 비트코드를 포함하면 새로운 버전의 앱을 앱 스토어에 다시 제출할 필요 없이 애플이 앱 바이너리(컴퓨터가 실행할 수 있는 코드를 포함하고 있는 파일)를 다시 최적화할 수 있다.

iOS 앱에서 비트코드는 기본으로 포함되지만 이를 선택할 수 있다. watchOS와 tvOS 앱에서 비트코드는 필수다. 만일 비트코드를 제공한다면 모든 앱과 앱 번들 안의 모든 프레임워크(프로젝트 안의 모든 타겟)는 비트코드를 포함해야 한다.

Xcode는 기본적으로 앱의 심볼을 숨기기 때문에 이는 애플이 읽을 수 없다. 앱을 앱 스토어에 올릴 때 심볼을 포함할 것인지에 대한 옵션이 주어진다. 심볼을 포함하면 테스트플라이트 혹은 앱 스토어를 통해 앱을 배포했을 때 애플은 앱에 대한 크래시 리포트를 제공한다. 만약 크래시리포트를 직접 수집하고 상징화(Symbolication : 상징화는 크래시 로그의 메모리 주소를 사람이 읽을 수 있는 함수명과 라인 넘버로 교체하는 작업을 말한다.)하기를 원한다면 심볼을 업로드할 필요 없다. 대신 앱 배포 후 비트코드 컴파일 dSYM 파일을 다운로드 받을 수 있다.

모든 뜻을 이해할 순 없었지만 내 프로젝트에서 에러가 발생하는 이유는 추측할 수 있었다. Xcode를 통해 프로젝트를 생성하면 기본적으로 비트코드는 포함되도록 설정되어 있다. 하지만 네이버 지도 V3 API 프레임워크는 비트코드를 포함하고 있지 않기 때문에 문서의 내용 중 *”만일 비트코드를 제공한다면 모든 앱과 앱 번들 안의 모든 프레임워크(프로젝트 안의 모든 타겟)는 비트코드를 포함해야 한다.”*에 위배된다. 그래서 프로젝트의 빌드 세팅에서 비트코드 포함 옵션을 No로 지정하니 프로젝트가 정상적으로 빌드 되고 실행될 수 있던 것이다.

하지만 여전히 비트코드 이 자체에 대한 이해는 부족하다. 좀 더 자료를 찾아보자. 역시 나와 비슷한 사람은 많았고 누군가 Quora에 질문을 올렸고 이에 대한 답변이 애플의 공식 문서보다 친절하고 보다 명확하게 와닿았다.

LLVM

비트코드를 이해하기 위해선 먼저 LLVM(Low Level Virtual Machine)에 대해 알아야 한다. LLVM은 라이브러리로 코드를 중간 매체 혹은 기계 코드로 컴파일하는데 사용된다. LLVM을 통해 많은 컴파일러와 언어들을 만들어낸다.

이렇게 만들어진 컴파일러의 컴파일 과정은 세 단계로 나뉜다.

  1. 컴파일러 프론트 엔드는 소스 코드를 중간 표현 단계로 변환한다.
  2. 이 중간 표현 단계는 불필요한 코드를 제거하고 하는 등의 최적하 과정을 겪는다. 이 과정은 소스 코드도 기계 코드도 아닌 중간 표현 단계에서 진행되는데 옵티마이저가 더욱 쉽게 해석할 수 있는 형태이기 때문이다.
  3. 컴파일러 백 엔드가 중간 표현 단계를 기반으로 기계 코드를 생성한다.

비트코드는 LLVM을 통해 앱의 코드를 받아 이를 비트코드로 전환하고 주어진 지침을 통해 이를 실행 가능한 앱으로 전환하는 방법을 안다. 즉 비트코드는 어떤 아키텍쳐에서도 실행되기를 준비하는 중간 단계인 것이다. 간단히 말해서, 이러한 구조는 애플이 앱 스토어에 새로운 CPU 지원을 백엔드에 쉽게 추가할 수 있다는 것을 의미하며 이렇게 되면 비트코드는 이를 통해 새로운 아키텍처로 컴파일 하는 방법을 알 수 있는 것이다.

비트코드를 포함하지 않는다면 컴파일러는 머신 코드만을 포함하는 실행 파일을 생성할 것이다.

하지만 비트코드를 포함한다면 비트코드는 기계 코드와 나란히 실행 파일에 포함될 것이다.

비트코드의 형태로 앱 스토어에 제출하면 앱 스토어는 해당 앱을 다운로드 받는 디바이스 환경에 맞춰 최적화를 진행하여 내려보낼 것이다. 이 과정은 앱 시닝(App Thinning)에 포함된다. 앱 시닝에 대한 내용은 추후에 살펴보고 포스팅할 예정이다.

일례로 살펴보면 애플은 2013년에 아이폰 5s부터 64비트 칩셋으로 교체할 것이라고 발표했고 앱 개발자들은 이를 위해 앱을 다시 컴파일해서 제출해야 했다. 그 이유는 비트코드가 아닌 실행 가능한 코드 자체를 올렸기 때문에 이는 새로운 아키텍쳐 칩셋 환경에서 동작할 수 없었기 때문이다. 이제는 비트코드가 포함된 실행 파일을 올려도 새로운 디바이스 환경에서 앱이 동작할 수 있게 되었다.


참고 자료