ABI Stability

스위프트 5.0에서 가장 많이 주목을 받고 있는 부분이 바로 ABI 안정화(Stability)이다. 대체 ABI가 안정화된다는 것이 무엇을 의미하는지, 왜 ABI 안정화를 지원하게 됬는지 ABI 자체가 무엇인지에 대해 알아보려 한다.

기본적으로 Swift ABI Stability Manifesto 글을 기반으로 각종 블로그 글들을 참고하면서 나름대로 정리해보았다.


The Big Picture

현재 스위프트의 가장 최우선 순위는 향후 스위프트 버전과의 호환성(compatibilty)이다. 호환성은 다음의 두 가지 목표를 갖고 있다.

  1. 소스 호환성(Source compatibility)은 새 컴파일러가 구 버전의 스위프트를 컴파일 할 수 있다는 것을 의미한다. 이는 새 스위프트 버전이 나오면 스위프트 개발자들이 직면했던 마이그레이션의 고통을 줄여주는 목적을 갖고 있다. 소스 호환성 없이는 프로젝트는 버전 잠금(version-lock)에 직면하는데 이는 프로젝트와 패키지 내부의 모든 소스코드가 동일한 스위프트 버전으로 작성되어야 한다는 것을 의미한다. 소스 호환성이 존재한다면 패키지 작성자는 그들의 사용자가 새로운 버전의 스위프트를 사용할 수 있도록 하며 여러 스위프트 버전을 단일 코드 기반으로 유지할 수 있다.
  2. 바이너리 프레임워크와 런타임 호환성(Binary framwork & runtime compatibility)은 다양한 스위프트 버전에서 동작할 수 있는 바이너리 형태의 프레임워크 배포를 가능하게 한다. 바이너리 프레임워크는 프레임워크 API의 소스-레벨 정보와 통신하는 스위프트 모듈 파일(Swift module file)과 런타임 중 로드되는 컴파일된 구현체인 공유 라이브러리(shared library)를 포함한다. 따라서 바이너리 프레임워크 호환성(binary framework compatibility)은 두 가지 목적을 갖고 있다.
    • 모듈 포맷 안정성(Module format stability)은 컴파일러가 프레임워크의 공개 인터페이스를 나타내는 모듈 파일을 안정화시킨다. 이는 API의 선언과 inlineable 코드를 포함한다. 이 모듈 파일은 컴파일러가 프레임워크를 사용하는 클라이언트 코드를 컴파일 할 때 타입 검사, 코드 생성 등과 같은 필수 작업을 진행하는데 사용된다.
    • ABI 안전성(ABI stability)은 서로 다른 스위프트 버전으로 컴파일된 어플리케이션과 라이브러리 사이의 바이너리 호환성을 가능하게 한다.

What is ABI?

런타임 중에 스위프트 프로그램 바이너리는 ABI를 통해 다른 라이브러리와 요소들과 상호작용한다. ABI는 Application Binary Interface를 의미하며 독립적으로 컴파일된 바이너리 엔티티(실체)들이 서로 연결되고 실행되기 위해서 반드시 따라야 규격이다. 이러한 바이너리 엔티티들은 함수를 호출하는 방법, 메모리에서 데이터가 표현되는 방법 그리고 그들의 메타데이터가 어디에 존재해야하는지 그리고 어떻게 접근해야하는지 등의 저수준의 상세 사항들을 따라야 한다.

API를 사용할 때 우리는 사용하려는 기능의 내부 로직은 크게 신경쓰지 않고 API의 원하는 기능을 취할 수 있다. 라이브러리가 업데이트되어도 우리가 호출하는 API의 메소드의 외형은 동일하게 사용할 수 있어야 한다. 라이브러리가 업데이트될 때마다 외형이 변경된다면 우리는 계속해서 우리의 코드를 이에 맞춰 수정해야 한다.

ABI도 마찬가지라고 생각한다. ABI의 안정화 없이 스위프트 버전이 올라가게 되면 우리는 프로젝트에서 사용되는 스위프트를 새 버전의 스위프트로 계속해서 마이그레이션 해야 한다.

What is ABI Stability?

ABI 안정화란 향후 새로운 버전의 컴파일러가 안정환된 ABI 규격을 따르는 바이너리를 생성할 수 있는 수준으로 만드는 것을 의미한다.

ABI 안정화는 오직 외부에 노출되는 공공 인터페이스와 심볼의 불변성에만 영향을 미친다. 내부 심볼, 컨벤션 그리고 레이아웃은 ABI 규격을 깰 필요 없이 지속해서 변경할 수 있다. 예를 들어 미래의 컴파일러는 공개되어 있는 공공 인터페이스를 유지하는한 내부 함수 호출의 호출 규칙을 자유롭게 변경할 수 있다.

What Does ABI Stability Enable?

ABI 안정화는 OS 공급자가 운영체제에 스위프트 표준 라이브러리와 구 버전 혹은 새 버전의 스위프트로 만들어진 어플리케이션과의 호환성을 갖는 런타임을 내장할 수 있도록 한다. 이렇게 되면 이러한 플랫폼 상의 앱을 배포할 때 표준 라이브러리를 앱 내에 포함시켜 배포할 필요가 없어진다. 이는 도구의 의존성을 줄여주고 운영체제에 보다 우수한 조화를 가능하게 한다.

기존의 iOS 앱 번들에는 해당 앱을 만드는데 사용한 버전의 스위프트 표준 라이브러리를 포함하고 있었다. 즉 스위프트 4.2로 만들어진 앱은 스위프트 4.2 ABI를 포함하는 스위프트 4.2 동적 라이브러리를 앱 번들 내에 포함하고 있었고 스위프트 3.0으로 만들어진 앱은 3.0 ABI를 포함하는 동적 라이브러리를 앱 번들 내에 포함하고 있었다는 것이다.

즉 각각의 언어는 각각의 OS 버전과 서로 다른 ABI 규격을 갖고 있었기 때문에 다른 버전의 OS에서 앱이 실행되기 위해선 앱 번들 자체에 스위프트 동적 라이브러리를 포함했어야 했다.

ABI가 안정화되면 이렇게 앱 번들 내에 해당 버전의 스위프트 다이나믹 라이브러리를 포함할 필요가 없기 때문에 앱 사이즈는 줄어들 수 있다. 왜냐하면 OS와 스위프트의 버전 차이가 존재해도 ABI 규격은 동일하기 때문이다. 스위프트 표준 라이브러리와 스위프트 런타임이 OS에 내장되는 것이다.

Module Stability

ABI 안정화는 런타임 중의 스위프트 버전들의 혼용에 관한 것이다. 컴파일 시점은 어떤가? 스위프트는 “swiftmodule”이라는 불투명한 아카이브 포맷을 사용해 수동으로 작성된 헤더 파일이 아닌 “MagicKt” 프레임워크와 같은 라이브러이의 인터페이스를 나타낸다. 그러나 “swiftmodule” 포맷 역시 현재 컴파일러 버전에 묶여잇고 이는 만일 “MagicKit”이 다른 스위프트 버전으로 만들어졌다면 개발자는 import MagicKit을 통해 해당 프레임워크를 사용할 수 없다는 것을 의미한다. 즉 앱 개발자와 라이브러리 제작자는 반드시 같은 버전의 컴파일러를 사용해야 한다.

이러한 제한 사항을 없애기 위해 라이브러리 제작자는 현재 구현중인 현재는 구현되고 있는 모듈 안전성(module stability)라 불리는 기능을 필요로 한다. 이를 통해 라이브러리를 사용하는 개발자는 모듈이 어떤 컴파일러로 만들어졌는지 생각할 필요 없이 모듈을 사용할 수 있다.

예를들어 스위프트6 그리고 스위프트7 컴파일러는 스위프트6로 만들어진 프레임워크의 인터페이스를 읽을 수 있는 것이다.

Libraray Evolution

우리는 지금까지 컴파일러 교체에 관해 얘기했지만 스위프트 코드도 동일하다. 오늘날 스위프트 라이브러리가 변경되면 해당 스위프트 라이브러리를 사용하는 앱은 재컴파일 되어야 한다. 이는 몇 가지 장점이 있는데 컴파일러가 앱이 사용하는 라이브러리의 버전을 정확히 알고 있기 때문에 코드 크기를 줄일 수 있는 추가적인 가정(assumption)을 할 수 있고 앱을 보다 빠르게 실행시킬 수 있다. 하지만 이러한 가정은 다음 라이브러리 버전에는 맞지 않을 수 있다.

Library Evolution 기능은 앱을 재컴파일 할 필요 없이 새로운 버전의 라이브러리의 기능을 사용할 수 있도록 하는 것이다.

위의 예제에서 앱은 노란색 버전으로 만들어진 프레임워크로 만들어졌다. library evolution과 함께 노란색 버전을 가진 시스템에서의 실행은 물론이고 새롭게 개선된 빨간색 버전에서도 실행될 수 있다.


참고 자료