A primer on libraries, frameworks, and linking, and an explanation of what’s meant by static and dynamic. We’ll finish by learning about the new mergeable libraries.
If you want to embarrass a senior iOS engineer, ask them to explain the difference between Dynamic Frameworks and Static Libraries.
These concepts are enormously important, but for years, they were packaged up in a box in my brain labelled “I’ll learn about these one day, but until then, I’ll have impostor syndrome”.
Unless you’re working to reduce app bundle size, simplify your dependency graph, optimise launch performance, or speed up build times, you won’t get to put this knowledge into practice.
Let’s change that with a primer on libraries, frameworks, and linking, and then let’s understand what is meant by static and dynamic. We’ll finish by learning about the new mergeable libraries available in Xcode.
Libraries vs Frameworks
Libraries and Frameworks are actually very straightforward if you approach them with fresh eyes.
The main reason they feel confusing is because they are usually explained very poorly.
Libraries
Libraries are pure code. They can be imported into your projects to provide reusable classes and functions. Libraries can be static (.a) or dynamic (.dylib). We’ll look at the differences between static and dynamic libraries later in the section on linking.
Frameworks
Frameworks are folders which contain a library. This library can be either static or dynamic, which the folder inherits — it becomes a static framework if it contains a static library, and a dynamic framework if it contains a dylib.
Alongside the library, framework folders contain additional resources & metadata, including:
Asset catalogues
Strings, Nib files, Obj-C headers, & metadata
Code signatures
Documentation
Here’s what a real (dynamic) .framework folder looks like:
You can see the assets, code signature, and Info.plist metadata. The code itself is packaged into a binary (Unix Executable File). This is compiled and ready to link to the app.
Library Assets & Bundles
Libraries themselves do not contain assets, but you can create them with a resource bundle (in Swift Packages this is named Media by default). These resources are included in the app as part of a .bundle file.
This sample app includes a TavernUI library, containing a design system & assets. The code from the TavernUI library is linked directly into the main executable, TavernTools. The TavernUI.bundle file which contains the assets was packaged separately inside the main .app bundle.
What is Linking?
In Computer Science, a linker combines compiled object files together into a single executable entity.
If you work for a company with a large, complex app, it probably consists of a main target (plus extensions), several feature modules, additional layers for core utilities, and third-party dependencies.
These modules are processed by the Swift Compiler, then the linker… well, links these compiled modules together. They are ultimately merged into a single program that the OS can run: your app.
When does this linking happen? It depends.
Static Linking
Static linking is the last stage of the Swift compiler. After processing Swift through SIL, optimisations, and LLVM IR, it ends up as machine code (a.k.a. ARM assembly) inside object files (.o) which look like this:
mov x0, #0x0 ; Move 0 into register x0
bl _printf ; Branch to the printf function
ret ; Return from the functionThis assembly code is represented in the object file as TEXT. As well as this finely-optimised machine code, object files also contain:
A symbol table, which maps functions and variables to memory addresses in your running program.
Global and static variables, represented in the file as
DATA.Debug information, a.k.a.
DWARF.
Static Copying
If your library or framework is static, then its compiled object files are copied into the main Unix executable during static linking.
This copying is critical to understand: the performance characteristics of static vs dynamic linking are all downstream consequences of this compile-time copying.
If you have large libraries, it takes a long time to copy them all into the binary. Even worse, if your app has a complex dependency graph, importing many inter-dependent libraries, this copying will happen repeatedly, even for incremental builds. To developers, this manifests as a longer build time.
After static linking finishes, we are left with a single Mach-O executable. This consists of the compiled object files in your main app, linked with the object files from all the static libraries & frameworks throughout your dependency graph.
This single statically-linked executable is loaded up by the OS to run your app. Since it’s just a single file, this launches pretty fast.
This makes static linking pretty great for end-users. This UX benefit is why non-system modules are statically linked into your app by default.
Static Bloat
If you have a big or complicated app, you may include extension targets such as widgets or a notification share extension.
If you’re not careful, you might experience the biggest drawback to static linking: static libraries & frameworks — alongside their associated resource bundles — are copied into every target.
This can dramatically bloat the size of your app.
Dynamic Linking
Dynamically linked libraries and frameworks are linked at runtime by a system called dyld.






