[iOS] - Xcode iOS Framework vs Static Library


Xcode를 통해 만들 수 있는 프레임워크와 정적 라이브러리에 대해 자세히 알아보자.


iOS 앱을 개발할 때, 모든 것을 다 직접 개발하는 일은 드물다. OS 뿐만 아니라 오픈 소스 커뮤니티가 바로 사용할 수 있는 이미 만들어진 기능들을 제공하기 떄문이다. 이런 기능들은 배포 가능한 형태인 라이브러리로 포장되어 있다.

이미 만들어진 기능을 사용하기 위해 라이브러리나 프레임워크를 사용하기도 하는데, 또 중요한 것은 클린 아키텍처를 지원하는 것이다. 코드를 모듈로 관리하는 방법에는 라이브러리, 프레임워크, Swift package가 있다.

Xcode에서 처음 iOS 프로젝트를 만들 때 볼 수 있는 창이다.

image

이 중 “Framework & Library”의 Framework, Static Library, Metal Library에 대해 알아보자. 참고로 macOS같은 경우에는 아래와 같이 더 많은 종류의 프레임워크, 라이브러리 생성이 가능하다.

image

Framework? Library?

프레임워크와 라이브러리는 항상 머리로는 이해하고 있다고 생각하지만 정확히 둘의 차이를 말해보라 하면 쉽게 말이 나오지 않는 개념이다. iOS의 프레임워크, 라이브러리를 보기 전에 먼저 둘의 개념부터 확실하게 정리하자.

쉽게 이해할 수 있게 정리하면, 프레임워크는 개발자가 제품을 만들기 위한 기반이 된다면, 라이브러리는 제품을 만드는데 도와주는 도구의 역할을 한다.

Framework

Framework라는 단어는 뜻부터가 뼈대다. 즉 개발을 하기 위해 뼈대를 제공해주는 애들을 의미한다. 프레임워크는 소프트웨어 개발, 즉 소프트웨어의 특정 문제를 해결하기 위해 상호 협력하는 클래스와 인터페이스의 집합이다. 프레임워크가 뼈대가 되니, 우리는 이 뼈대에 맞게 개발을 하면 된다. 비행기를 만드는 방법과 배를 만드는 방법이 다르고, 이 둘을 만들기 위해 필요한 기본적인 도구들이 다를 것이다. 우리는 이 도구들이 제공해주는 뼈대, 기능을 사용해서 우리가 원하는 제품을 만들면 되는 것이다.

image

개발자들이 개발할 때 흔히 사용하는 ruby, Django(장고), Spring, Anuglar 등이 프레임워크에 속한다. 프레임워크 개념 자체는 좀 생소할 수 있어도, react native나 spring boot같은 예시를 들면 좀 친숙하게 느껴질 것이다. 이 프레임워크는 각각의 목적에 맞게 프로그램을 쉽게 만들기 위한 요소와 틀을 제공해서 생산성과 품질을 높여준다.

정리하자면 프레임워크는 소프트웨어의 특정 문제를 해결하기 위한 클래스, 인터페이스의 집합으로, 어떤 프로그램을 개발하기 위한 여러 요소들과 틀을 제공하는 프로그램이다.

Library

라이브러리는 특정 기능에 대한 도구, 함수들의 집합을 의미한다. 비행기를 만든다고 결정했다면 제작하는데 필요한 도구를 망치를 사용해도 되고, 전문 기계를 써도 되고, 비행기를 만든다는 것만 지키면 만드는 방법은 자유다. 프로그램도 마찬가지로, 특정 기능을 구현하는데 도움이 되는 도구를 쓰는 건 개발자의 자유다.

예를 들어 인공지능 모델을 개발하겠다고 하면, Tensorflow를 쓸 수도 있고, Keras, Pytorch 등 여러 라이브러리 중 아무거나 사용해도 된다. 이처럼 특정 기능을 구현하기 위한 도구들의 집합을 라이브러리라고 한다.

image

Framework과 Library의 차이점, 공통점

우리는 정해진 프레임워크에서 라이브러리를 자유롭게 사용할 수 있다. 즉 자유도에서 차이가 난다.

프레임워크로 프로그램을 개발하면, 프레임워크가 정해주는 틀에 맞게 개발을 해야 한다. 하지만 라이브러리는 어떤 것을 사용하든 개발자의 마음이기 때문에 자유롭다.

하지만 둘 다 모두 프로그램의 개발을 도와주고 생산성을 높여주는 것이라는 공통의 목적이 존재한다.


이제 프레임워크와 라이브러리의 차이는 확실하게 다시 짚어봤다. 그렇다면 Xcode에서 생성할 수 있는 iOS Framework과 라이브러리는 무엇일까?

iOS를 개발할 때 프레임워크와 라이브러리는 무엇이 있을까? UIKit, Foundation, WatchKit, GameKit 등 이미 익숙한 것들이 그 예다. 얘네들은 Apple 의 표준 라이브러리고, 모든 프로젝트에서 흔히 사용하고 있을 것이다. 또한 Xcode 프로젝트에 사용하는 third-party를 관리해주는 CocoaPods와 Carthage도 들어본 적 있을 것이다.

기본

Swift는 모듈로 코드를 구성한다. 모듈은 코드 배포에서의 하나의 단위로 나타나진다. 프레임워크, 라이브러리, swift package와 build target은 Xcode에서 별개의 모듈로 다뤄진다.

Bundle은 하위 디렉토리를 가지고 있는 파일 디렉토리다. iOS에서, bundle은 연관된 파일을 하나의 패키지로 묶는다. 예를 들어 이미지, nib, 컴파일된 코드가 여기 포함될 수 있다. 시스템은 이를 하나의 파일로 취급하고 개발자는 내부 구조를 알지 않고도 bundle resource에 접근할 수 있다.

Souce file은 모듈에 포함된 Swift 소스 코드 파일 하나다.

Executable 은 앱의 메인 binary이다.

Object file은 바로 실행가능하지는 않은 기계어와 유사한 형태의 object code를 포함한 파일이다. Object file은 공유 라이브러리로도 동작할 수 있다.

Object code는 컴퓨팅에서 컴파일러의 산출물이 된다. 일반적으로 object code는 기계어와 같은 컴퓨터 언어에서의 명령어 같은 것이다.

iOS에서 Library란?

라이브러리는 앞서 특정 기능을 구현하기 위한 도구들의 집합이라고 했다. iOS에서 라이브러리는 Xcode target에 포함되어 있지 않은 코드와 데이터를 정의하는 파일들이다.

외부 라이브러리와 앱의 소스 코드 파일들을 합치는 과정을 linking이라 한다. Linking을 통해 아이폰이나 맥에서 실행가능한 단일 실행 파일이 나오는 것이다. Linking 외에도 모든 Xcode 프로젝트는 실행가능한 앱을 생성하기 위해 4단계를 더 거친다.

라이브러리는 앱에 어떻게 link되는지에 따라 두 개로 나뉜다.

  • 정적 라이브러리: .a
  • 동적 라이브러리: .dylib

추가로, 특별한 종류의 라이브러리도 있다.

  • 텍스트 기반 .dylib stub: .tbd (stub은 토막, 남은 부분으로 dummy가 실제 동작하는 것처럼 보이게 만든 것으로, 인터페이스/기본클래스가 최소한으로 구현되어 있다.)

iOS에서 Framework란?

프레임워크는 다이나믹 라이브러리, 문자열, 헤더, 이미지, 스토리보드 등과 같은 리소스를 포함할 수 있는 패키지다. 구조에 조금만 변화를 주면 다른 프레임워크를 포함하게 할 수도 있다. 이렇게 다른 애들을 포함하는 애들을 umbrella framework라고 한다.(애플은 이를 사용하는 것을 추천하지 않는다.)

프레임워크는 .framework라는 확장명을 가진 번들이다. 코드에서 NSBundle / Bundle 클래스로 접근할 수 있고, 대부분의 번들 파일과는 다르게 파일 시스템에서도 열 수 있어서 개발자들이 컨텐츠를 확인하기 쉽다. 프레임워크는 프로그램의 옛날 버전을 지원하기 위해 코드와 헤더의 여러 복사본을 저장하는 것을 가능하게 하는 versioned bundle format을 가지고 있다. 번들 구조에 대해 더 자세히 보려면 애플의 Bundle Programming Guide를 확인하면 된다.

라이브러리가 오직 실행가능한 코드만 가지고 있고, 에셋은 가질 수 없지만 프레임워크에는 단일한 패키지에 아무거나 넣을 수 있다.

아키텍처와 클릭 디자인 관점에서 프레임워크와 라이브러리 간의 차이를 보는 또 다른 관점이 존재한다. Martin Fowler의 “Inversion Of Control” 글에서는 ioc가 프레임워크가 라이브러리와는 다른 차이점을 만들어주는 핵심 요소라고 설명한다.

  • 라이브러리는 주로 클래스로 구성되는, 내가 호출할 수 있는 함수들의 집합이다. 이런 함수들을 호출하는 것은 특정 동작을 하고 클라이언트에 제어권을 넘긴다.
  • 프레임워크는 특정 동작을 내부에 구현한 추상적인 디자인을 가지고 있다. 이를 사용하려면 상속이나 내 클래스에 plugging함으로써 내가 하려는 동작을 프레임워크의 다양한 곳에 주입해야 한다. 이러면 프레임워크의 코드는 특정 시점에 내 코드를 호출한다. 이러면 프로그램의 주도권이 역전돼서 내가 아닌 프레임워크에 넘어가게 된다.

Static Library

정적 라이브러리는 object files의 컬렉션이다. 여기서 object file이란 컴파일러에 의해 나온 기계어를 포함한 파일을 의미한다.

정적 라이브러리는 .a 로 끝나며 archiver 도구에 의해 생성된다. Object 파일은 iOS와 macOS os를 위한 특별한 형식인 Mach-O 형식을 가지고 있다. Object file은 아래의 요소들을 담고 있는 binary stream이다.

  • 헤더 : 파일의 target 아키텍처를 정의한다. 하나의 Mach-O가 한 구조를 위한 코드와 데이터를 가지고 있으므로, 예를 들어 x86-64를 위한 코드는 arm64에서 동작하지 않을 것이다.
  • Load commands: 파일의 논리적 구조를 정의한다. Symbol table의 위치같은 것이 그 예가 된다.
  • Raw segment data: 코드와 데이터를 포함한다.

여기서 Mach-O 파일이 하나의 아키텍처를 지원한다 했는데, 그렇다면 많은 정적 라이브러리를 포함한 Swift 앱이 모든 디바이스와 시뮬레이터에서 동작할 수 있는 것일까?

이는 lipo 도구가 존재하기 때문에 가능하다. 이 도구는 여러개의 단일한 아키텍처 라이브러리들을 하나의 fat binary로 패키징해준다.

image

정적 linker가 컴파일된 소스코드와 라이브러리 코드를 하나의 실행 가능한 파일을 만들고, 이는 런타임에 전체가 메모리에 로드된다. 정적 라이브러리가 object 파일들, 즉 기계어로 된 명령어들의 집합의 컬렉션이므로 이를 생성하고 배포하는데 몇 가지 제약이 존재한다.

  • 컴파일 코드처럼 라이브러리를 빌드해야 한다. 예를 들어 iOS 앱을 위한 라이브러리르 만들고 있다면 iOS 시뮬레이터와 iOS 기기를 위한 라이브러리를 생성해야 한다.
  • 라이브러리는 리소스 파일(이미지, 에셋, nib, 문자열 파일 등등)을 포함하지 못한다. 만약 이를 프로젝트에 포함시키려면 .a 파일과는 별개로 해야 한다.

Dynamic Library

정적 라이브러리와는 동적 라이브러리는 하나의 단일한 실행가능한 파일에 복사되기 보다는 실제로 필요한 순간에 메모리에 로딩된다. 이는 load time이나 런타임에도 발생할 수 있다.

동적 라이브러리는 주로 앱 사이에 공유되기 때문에, 시스템은 라이브러리의 오직 하나의 복사본을 저장하고 다른 프로세스들이 이에 접근할 수 있게 해야 한다. 결국 정적 라이브러리에서 코드를 호출하고 데이터를 가져오는 것은 정적인 애들에서 같은 작업을 하는 것보다 시간이 더 오래 걸린다.

모든 iOS와 macOS 시스템 라이브러리는 동적이다.

image

텍스트 기반 .dylib stub

우리가 UIKit나 Foundation과 같은 시스템 라이브러리를 link할 때, 이 파일이 너무 크기 때문에 전체가 우리 앱에 복사되는 것을 원하지는 않는다. Linker 또한 이 점에 엄격하고 공유되는 .dylib 라이브러리들을 허용하지는 않지만, .tbd는 허용한다.

.tbd는 동적라이브러리에 정의된 메서드들의 몸통을 제외한 이름만을 가지고 있는 텍스틑 파일이다. 따라서 일치하는 동적 라이브러리보다 더 적은 공간을 차지한다. 메서드 이름과 같이, 일치하는 .dylib에서의 위치, 아키텍처, 플랫폼, 그리고 다른 메타데이터를 가지고 있다. 아래는 .tbd의 한 예시다.

--- !tapi-tbd-v3
archs:           [ x86_64 ]
uuids:           [ 'x86_64: 6FFAC142-415D-3AF0-BC09-336302F11934' ]
platform:        macosx
install-name:    /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libQuadrature.dylib
objc-constraint: none
exports:         
  - archs:           [ x86_64 ]
    allowable-clients: [ vecLib ]
    symbols:         [ _quadrature_integrate ]
...

동적 vs 정적 라이브러리

동적, 정적 라이브러리의 장단점에 대해 알아보자.

정적 라이브러리

장점

  • 정적 라이브러리는 앱에 존재하는 것이 보장되어 있고, 올바른 버전을 가지고 있다.
  • 라이브러리 업데이트에 맞춰서 앱을 업데이트 할 필요가 없다.
  • 라이브러리를 호출할 때의 더 나은 성능을 가지고 있다.

단점

  • 앱이 차지하는 용량을 키운다.
  • 많아진 앱 실행파일 때문에 앱을 키는 시간이 더 오래 걸린다.
  • 하나의 함수를 이용하더라도 전체의 라이브러리를 복사해야 한다.

동적 라이브러리

장점

  • 앱을 다시 컴파일 하지 않고도 라이브러리가 발전했을 때 이를 앱에 적용할 수 있다. 시스템 라이브러리에서 이 점이 유용하다.
  • 앱 간에 공유되기 때문에 디스크 공간을 더 적게 차지한다.
  • 라이브러리가 런타임에 필요한 순간 로딩되기 때문에 앱을 키는 시간이 더 빠르다.
  • 조각조각 로딩되기 때문에 하나의 함수를 이용할 때 전체 라이브러리를 로딩할 필요가 없다.

단점

  • 만약 라이브러리에 변화가 있다면 프로그램에 문제가 생길 가능성이 있다.
  • 라이브러리가 앱 실행 파일 밖에 위치하기 때문에 라이브러리 호출이 상대적으로 느리다.

정리

라이브러리와 프레임워크는 모두 iOS 프로그램을 만들기 위한 기본 구성 요소다.

라이브러리는 코드와 데이터의 컬렉션이고, 프레임워크는 다른 라이브러리와 프레임워크를 포함해서 다양한 종류의 파일들을 가진 계층적인 디렉토리다.

라이브러리가 linking되는 방법에 따라 정적/동적 라이브러리로 구분된다.

  • 참조
    • https://www.vadimbulavin.com/static-dynamic-frameworks-and-libraries/
    • https://medium.com/@m.tabrizi/ios-library-vs-framework-30866c41f100
    • https://medium.com/@zippicoder/libraries-frameworks-swift-packages-whats-the-difference-764f371444cd