Differences Between iOS Library, Framework, and Swift Package
1
The Era of Multi-Module Codebases
As Alarmy expands from a simple "alarm clock" that wakes you up on time to a "morning wellness" service that helps you create a successful morning, we've been adding more and more features.
As the service scaled, our project codebase started to grow with it, which led to a few problems.
First, project build times started taking way too long.
Second, it became increasingly difficult to figure out what responsibilities were assigned to which modules and what features were included in them.
Finally, with multiple engineers frequently modifying specific modules, we experienced a lot of conflicts.
To solve these issues, our iOS team decided to transition to a multi-module codebase.
Before actually creating a multi-module codebase, I wanted to explore the different ways to build a modular codebase in iOS.
Library
Framework (including Apple's newly introduced XCFramework)
Swift Package
Good-to-Know Prerequisites
When talking about code structuring and access control, we usually use the word module.
Think of a module as a single code distribution unit. Frameworks, libraries, Swift packages, and build targets are all treated as independent modules in Xcode. Each module has its own namespace and access control.
Usually, each module has a specific problem domain it tries to solve. For example, AVFoundation provides audio and media-related objects to help engineers build audio and media products.
Therefore, each module can be reused repeatedly depending on the problem the engineer is trying to solve.
Bundle
A bundle is a file directory inside. In iOS specifically, a bundle serves to provide app-related files as a single package. The system treats it as a single file, making it easy to access without needing to know its internal structure.
Source file
You can think of this as a Swift file inside a module.
Executable file
The binary used for running the app.
Object code
Object code is generally the output produced by a compiler. You can usually think of it as a sequence of machine code.
Object file
A file that contains object code.
Library
A Library is a collection of code and data to be used in a target (app). It is then linked to the target (app) at compile-time or runtime. However, it cannot include resources like images.
Libraries can be divided into two types based on how they are linked to the target.
Static Library (
.a)Dynamic Library (
.dylib)
Static Library
A Static Library is a bundle of object files. It has an .a file extension and is created using an archiver tool.
It feels somewhat like zip archiving, and the concept is similar. In other words, a Static Library is basically multiple object files archived together.
💡 .a 는 UNIX 에서 ar tool 에서 사용되던 포맷임.
💡 오브젝트 파일은 Mach-O 라는 iOS, macOS 에서 사용하는 포맷을 가지고 있다.
기본적으로는 바이너리 스트림인데, 아래의 내용을 포함하고 있다.
- Header: 파일의 타켓 아키텍쳐를 명시함. Mach-O 는 하나의 타겟 아키텍쳐에 대한 코드와 데이터를 갖기 때문에, x86-64 용 코드는 arm64 에서 동작하지 않음
- Load commands: 심볼 테이블의 위치 같은, 파일의 논리적 구조를 명시화하고 있음
- Raw segment data: 로우 코드 및 데이터를 포함함The code used in the app is copied into the executable file at compile-time via a static linker. It is then loaded into the memory Heap area and used throughout the entire runtime of the app.
Static Libraries also have some limitations.
They must be built for the same processor architecture as the target being used (
x86-64,arm64, etc.).They cannot contain resource files (image, assets, nibs, strings file).
Dynamic Library
Unlike a Static Library, the code of a Dynamic Library is not copied into the executable file at compile-time. Instead, it is loaded dynamically when needed. This has the advantage of being usable without needing to compile (create a new build) every time.
All iOS and macOS system libraries are Dynamic Libraries. Thus, apps deployed to iOS and macOS can benefit from system library updates even without shipping a new build.
Because they are not loaded into memory at app compile-time, code access speed is relatively slower compared to a Static Library.
(However, this is beneficial for speeding up the app's startup time.)
Framework
A Framework refers to a directory that has a hierarchical structure. It wraps dynamic libraries, header files, and resources (storyboards, image files, localized string) all in a single package.
When using a Framework, it must be included inside the app bundle.
You can think of a Framework like a Library, as a collection of code sharing the same purpose (the same domain).
However, a Framework has the following differences from a Library:
It can include resources. (For example, image, asset, documentations, strings)
Only one instance of the Framework's code and resources can be loaded into memory.
This reduces the memory footprint and can improve system performance.
Also, if multiple apps need the framework, it is shared among them.
XCFrameworks
XCFrameworks is a new way to distribute frameworks, introduced in Xcode 11.
The advantages of XCFrameworks are as follows:
They can be distributed containing multiple variants (device, simulator, and any platform supported by Xcode).
They support Swift and C-based code binaries.
They can bundle Static Libraries and headers.
Swift Package
Swift is a cross-platform language and requires tools capable of building across multiple platforms. One of the goals of SPM (Swift Package Manager) is to make distributing Swift code simple.
A Swift Package includes 소스 파일 and 매니페스트 파일. The manifest file describes how this particular Swift Package is structured. Swift Packages are distributed in source format. Therefore, you don't have to worry about binary compatibility; SPM automatically defines the Swift Package and links it for use in the actual app.
So, if you plan to distribute via source code, Swift Package is an excellent tool. Since Xcode 11, it has become incredibly easy to create and distribute.
A Swift Package generally consists of 3 major parts:
Dependencies
Targets
Products
Dependencies:
Specifies other Swift Packages used by the current package.
(Indicates the actual source locations and versions.)
Targets:
In Apple's documentation, a Target is described as "The basic building block of a Swift package."
You can think of it as the basic build unit.
Since Xcode 12, in addition to Swift, Obj-C, C++, and C, they can also include resources like images, storyboards, and JSON files.
Products
These are the outputs of a Package, such as a Library or an Executable, and are visible to external packages as well.
Swift Packages might seem like the ultimate choice since you don't have to worry about binary compatibility, Xcode manages dependencies and versioning for you, and they can include various resources. However, for B2B companies that need to distribute frameworks, it might be a difficult option to choose since they may be reluctant to share their source code.
Conclusion
Before creating a multi-module codebase, we explored the different modularization methods. When applying them to an actual project, you must fully understand the limitations of each method.
Applying too many Static Libraries can cause the app binary to become very large, increase app launch time, and use a lot of memory. Frameworks can utilize system resources more flexibly than Static Libraries and can additionally include resources like images. And if you are able to distribute source code, Swift Package is a highly appropriate method because Xcode will automatically handle dependencies, versioning, platforms, and more.
When it comes to modularizing our internal codebase, using Swift Packages seems to be the most effective approach.
References
Bundle
Libraries
Frameworks
Swift Package