Quick notes on swift libraries

# March 20, 2025

I've been dipping my toe back into the macOS development scene after a long hiatus focusing only on the web. After being spoiled by the package ecosystem of Python+JS1, I was wondering how different the Swift scene looks from the Objective-C scene of yore.

Overall I was pleasantly surprised about the state of the ecosystem:

  • Native installation of external packages right in Xcode, without having to manually download tarballs and mess around with import locations.
  • Community supported indexes for finding new packages.
  • Framework templates in Xcode for starting and building a new library.

Swift Package Manager

Screenshot of Xcode Package Search

The Swift Package Manager is bundled into Xcode as a UI and a command line entrypoint.

Apple provides a few hand-selected libraries that are included by default. After that you can add other indexes that host packages.2 Swift supporst a special JSON-based collections format that you need to add to the Manager. There isn't the same equivalent of a single maintainer-blessed index like with Python or JS, but Swift Package Index seems pretty close.

For Swift Package Index in particular, they segment their collection indexes by package author. So you'll have to add each author's packages one-by-one:

https://swiftpackageindex.com/vapor/collection.json

Library types

Within the deployment camp, there are two main outputs: Frameworks and Libraries. Frameworks are more native to macOS, libraries (also just known simply as statics) are the same compressed machine code that you would see on any operating system after a compiler has run.

Framework Target:

  • Bundle: A framework is a self-contained bundle that can include not only the compiled code but also resources (like images, nibs, storyboards, and configuration files). This makes it ideal if your library has associated assets.
  • Module Metadata: Frameworks come with module information (or module maps) that make it straightforward for Swift (and Objective-C) projects to import and use the API without additional configuration.

Static Library:

  • Compiled Code Only: A static library is simply a compiled archive (typically a .a file) containing code. It does not bundle resources, so if you need to distribute assets with your library, you’d have to manage them separately.
  • Manual Module Setup: In Swift projects, using a static library often requires extra steps (like creating module maps or bridging headers) to expose the API cleanly.

You'll almost always want to go with a framework.

Building a new library

You can start building a library in Swift or in Objective-C. And if you build one in Objective-C, Swift code can still read your exported code thanks to its .modulemap and header files. So you're future proofed either way. I'm starting mine in Objective-C.

Screenshot of project details

Project config lets you dynamically or statically link to frameworks just like you would in a project. So you can refactor your code to your absolute heart's delight. Private frameworks importing other frameworks importing frameworks? Say that five times fast.

Screenshot of header file

The framework only starts with a single .h header file and no module, but implementing a function for export is basically as easy as adding one to a regular project.

// Hello world function declaration
FOUNDATION_EXPORT NSString* HelloWorld(void);

From there, you can create a matching test_framework.m file and handle the concrete implementation:

#import "test_framework.h"

NSString* HelloWorld(void) {
    return @"Hello, World!";
} 

Once your library is built, Xcode puts it in a conventional place on disk. Not in the same working directory, which I felt a little counter-intuitive.

~/Library/Developer/Xcode/DerivedData/-/Build/Products/Debug/MyFramework.framework

An easier way to locate it:

Product (top bar) -> Show Build Folder in Finder

Screenshot of framework files

Compiled frameworks also contain basically what you'd expect. A bunch of support files for module definitions and metadata, then the actual framework logic compressed into an executable file. To use it in a code project, drag the artifact on disk into your project and copy source contents. It seems like you should be able to either link it by reference or copy the source, but when linking by reference it kept failing during the build:

Framework 'test_framework' not found
Linker command failed with exit code 1 (use -v to see invocation)

Only copying ended up succeeding:

Screenshot of copy

I also had to embed and sign for the executable to run without runtime errors. If you don't, your app may launch successfully but will crash the second you hit code that requires your library. Xcode can see your framework during compile time but during runtime there's no DYLD_FRAMEWORK_PATH pointing to the right path. Embedding custom frameworks is almost always the way to go.

  Reason: tried: '/Users/piercefreeman/Library/Developer/Xcode
  /DerivedData/test-project-avlrkthwcjkzxlfjmffogjxiukgm/Build/Products
  /Debug/test_framework.framework/Versions
  /A/test_framework' (no such file)

Screenshot of embedding

Once everything's wired up you can access your full Objective-C library from your swift code. In our case - generating the UI text string from the embedded framework:

Screenshot of working app


  1. Not just the package availability, but the searchable indexes as part of pypi/npm & the package manager tooling. They feel like communities where everything is open until proven closed. Objective-C packages always felt a bit like the opposite: closed until proven open. 

  2. Typically these indexes don't actually host the packages. Package source is hosted on Github and the indexes just host the metadata that communicates version numbers and hashes. 

👋🏼

Hey, I'm Pierce! I write mostly about engineering, machine learning, and building companies. If you want to get updated about longer essays, subscribe here.

I hate spam so I keep these infrequent - once or twice a month maximum. Promise.