Modular iOS

Distributing compiled iOS Swift static libraries and Swift static frameworks

Anurag Ajwani
Onfido Product and Tech
10 min readApr 20, 2020

--

Great libraries are ones that are easy to consume. They must be easy to install and use. So how can we make Swift static libraries easy to install and consume?

The installation process depends on how we deliver our static library. There are 2 ways we can deliver a Swift static library:

  1. Deliver the source code to the integrator
  2. Compile it and then distribute the compiled library

This post will focus on the second case.

In this post we will learn:

  • why should you distribute compiled static libraries
  • how to build compiled static libraries using Xcode
  • what are static frameworks and why you should distribute compiled static frameworks
  • how to build compiled static frameworks

For this tutorial I expect that you have a basic understanding of what a static library is. If you are not familiar with it feel free to check out my post Reusing code and resources with Swift static libraries and resource bundles. Feel free to skip any sections if you are already familiar with it. The sections are consumable on its own.

I have used Swift 5.2 and Xcode 11.4.1 for this post.

Why should you distribute your static library compiled?

When delivering our library compiled we avoid having the source code delivered to our integrator. If your company has secrets in the source code it would rather not reveal to the world then delivering your library compiled is a way to hide your company's secret.

Another reason to deliver your library compiled is to save the integrator time from compiling your code. This is true even if aren’t too concerned about revealing your code.

Thus there are two main reasons to deliver your static library compiled:

  1. not reveal our source code
  2. save app compilation time

How to distribute compiled static libraries

In this section we’ll look into compiling our static library through the Xcode interface. We’ll then integrate it with an app to test our compiled Swift static library.

In this section we will:

  1. Download the starter project
  2. Build a compiled static library
  3. Create an app to consume the static library
  4. Link the static library to the app

1. Download the starter project

First let’s download the starter project. The starter project contains a Swift static library ready to be consumed. Open terminal and run the following commands:

cd $HOME
curl https://github.com/anuragajwani/compiled_swift_static_lib/archive/starter.zip -o compiled_swift_static_lib.zip -L -s
unzip -q compiled_swift_static_lib.zip
cd compiled_swift_static_lib-starter/MyStaticLib

2. Build a compiled static library

Next let’s open the static library project. Run the following command:

open -a Xcode MyStaticLib.xcodeproj

Once Xcode opens up select MyStaticLib scheme from the Xcode toolbar and then select a iPhone simulator from the device list.

Selecting build scheme and device from Xcode toolbar

Next from menu select Product > Build.

Menu build option

Then open project navigator, from menu select View > Navigators > Show Project Navigator.

Menu Show Project Navigator

You’ll notice under MyStaticLib > Products folder a file named libMyStaticLib.a. Right click on libMyStaticLib.a and then select Show in Finder.

Opening libMyStaticLib.a in Finder

Finder will open up with the location of libMyStaticLib.a.

Finder showing the location of libMyStaticLib.a file

Notice that finder also shows a folder in the build folder named MyStaticLib.swiftmodule. We’ll come back to this later. Keep this window open for later convenience.

3. Create an app to consume the static library

Next we’ll create an app to consume our static library. Open Xcode and from menu select File > New > Project…

Create a new project

From project template choose Single View App.

Select template

On new project options name the product MyApp. Select the language as Swift. Select Storyboard as the user interface option. Keep all checkboxes unchecked. Click Next.

Name the project product MyApp

When prompted where to save the project hit Cmd ⌘ + Shift ⇧ + G keys. When prompted “Go to the folder:enter ~/compiled_swift_static_lib-starter and then click Go.

Save it in the same directory as MyStaticLib

Finally click Create.

4. Link the static library to the app

Now we have an app to consume our static library. Next lets link our static library to our app. Drag and drop libMyStaticLib.a into MyApp project.

Linking MyStaticLib in MyApp

When prompted “Choose options for adding these files” check Copy items if needed. Select Create Groups for the added folders option. Check the MyApp target in the “Add to targets:” list. Click Finish.

Add file options

Note the static library has now been added to the project and linked. You can check by selecting MyApp project settings in the project navigator then selecting the MyApp target in the editor pane. Next select General tab and under Frameworks, Libraries, and Embedded Content you should find libMyStaticLib.a.

Checking MyStaticLib is linked

Next let's call a function from MyStaticLib. Open AppDelegate.swift. After import UIKit add the following line:

import MyStaticLib

Then within the function in the AppDelegate class

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool

add the following line before the return true line:

functionA()

From the Xcode toolbar make sure to select MyApp scheme and a simulator device.

Selecting scheme and device

You’ll notice that Xcode complains about being unable to find MyStaticLib module even though we have linked it to MyApp.

Error encountered

The reason why Xcode is complaining is due to the fact that we have only linked the compiled code and not included the public interface to consume the compile code.

Remember earlier when we built MyStaticLib? There were 2 artefacts built:

  • libMyStaticLib.a file
  • MyStaticLib.swiftmodule folder

The .swiftmodule folder is the public interface for well … a swift module. That is for static libraries as well as dynamic frameworks. To make MyApp’s integration with MyStaticLib we must tell MyApp to search for public interfaces where the swiftmodule folder resides.

Drag and drop MyStaticLib.swiftmodule into the MyApp project directory in Finder.

Drag and drop MyStaticLib.swiftmodule into MyApp folder

Next open MyApp project in Xcode. Open project configuration settings (the one that says MyApp with a blue icon to its left on the project navigator). Select MyApp under targets in the editor area. Then select Build Settings tab. Search for Import Paths which lives under Swift Compiler — Search Paths.

Setting import paths for the public interface

The current value is empty. Change this to $(PROJECT_DIR). Xcode will automatically change the path to the current project directory path.

Build and run the app in any simulator. This time it should have built and ran successfully 🎉. Watch the console (View > Debug Area > Activate Console) to get a response from MyStaticLib.

Results from running MyApp in console

Next let’s build for devices. From the Xcode toolbar select the MyApp scheme and for devices select Generic iOS Device.

Building MyApp for Generic iOS devices

Now from menu select Product > Build. You should see the following error in the issue navigator (View > Navigators > Shoe Issue Navigator):

Error message

Why do we get this error? We get this error because our static library binary format is not compatible for devices. Remember in step 2 when we built our static library targeted to simulators? That means that our static library was only built for simulators. To support device we would have to target Generic iOS Device. However the compiled static library would not work for simulators then.

Therefore compiled static libraries are:

  • complex to integrate with
  • hard to manage when changing the target device between physical and simulator device

So is there a way to make the integration experience better? Yes there is! The answer is Swift static frameworks! The title of the post already gave it away didn’t it? 😅

What are static frameworks and why should you distribute your compiled static library as a static framework

In the previous section when we compiled a static library:

  • it was only compatible with devices or simulators, not both
  • integrators required to manage a separate directory for the public interface of the static library

Apple does not really offer other mechanisms of distributing modules which are statically linked for iOS out-of-the-box. However there is a way for us to make integration experience better than the one offered by Apple. The solution is called static frameworks.

In essence static frameworks imitate the structure of dynamic frameworks. Dynamic frameworks allow integrators to have a single artefact that is compatible with both devices and simulators.

Knowledge of dynamic frameworks is not required for this post.

How to distribute compiled static frameworks

In this section we will learn how to compile static libraries into static frameworks. Then we will integrate with the static framework in our consumer app from the previous section; MyApp.

In this section we will:

  1. Download the starter project
  2. Build a static framework from MyStaticLib static library
  3. Integrate MyStaticLib static framework into MyApp

1. Download the starter project

First let’s download the starter project for this section. The starter project contains the sane Swift static library as the previous section. It also contains the consumer app that we created. Open terminal and run the following commands:

cd $HOME
curl https://github.com/anuragajwani/compiled_swift_static_lib/archive/starter-static-framework.zip -o compiled_swift_static_lib.zip -L -s
unzip -q compiled_swift_static_lib.zip
cd compiled_swift_static_lib-starter-static-framework/MyStaticLib

2. Build a static framework from MyStaticLib static library

To build MyStaticLib we will only make use of the terminal. We will create a script that will build a static framework. Run the following command:

cat > build_static_framework.sh <<-EOF
# 0. clean build directories
rm -rf \
derived_data \
MyStaticLib.framework
# 1.1 Build static library for simulatorxcodebuild build \
-scheme MyStaticLib \
-derivedDataPath derived_data \
-arch x86_64 \
-sdk iphonesimulator
# 1.2 Build static library for iOS devicesxcodebuild build \
-scheme MyStaticLib \
-derivedDataPath derived_data \
-arch arm64 \
-sdk iphoneos
# 2. Create frameworkmkdir MyStaticLib.framework/# 3. Create binary compatible with devices and simulatorslipo -create \
derived_data/Build/Products/Debug-iphoneos/libMyStaticLib.a \
derived_data/Build/Products/Debug-iphonesimulator/libMyStaticLib.a \
-o MyStaticLib.framework/MyStaticLib
# 4.1 Create empty public interface directorymkdir MyStaticLib.framework/MyStaticLib.swiftmodule# 4.2 Copy public interfaces for device and simulators into static framework public interfaces directorycp -r derived_data/Build/Products/*/*.swiftmodule/* MyStaticLib.framework/MyStaticLib.swiftmodule
EOF

The above script will:

  1. Build the static framework twice; once for simulators and once for devices.
  2. Create a framework folder
  3. Create a universal binary which is compatible with simulators and devices. This step will use the static libraries built in the first step.
  4. Create the public interface of the framework. This step will also use the public interfaces built in step 1.

Next let’s run the script. Execute the following command:

sh build_static_framework.sh
screenshot of the script run

A framework, MyStaticLib.framework, will have been created within the same directory.

Verifying static framework is built

3. Integrate MyStaticLib static framework into MyApp

For our final step let’s integrate with MyStaticLib.framework. We’ll first need to link the created framework to MyApp. Execute the following command:

open . && open -a Xcode ../MyApp/MyApp.xcodeproj

Now MyApp project is open in Xcode and Finder with MyStaticLib.framework. Drag and drop MyStaticLib.framework into the MyApp project in Xcode.

Drag an drop the static framework into MyApp project

When prompted to “Choose options for adding these files:” make sure “Copy items if needed” is checked. Select Create Groups for “Added folders option:”. Finally make sure the MyApp target is also checked. Click Finish.

Add files options

Before we can run our static library code we must make sure that the public interface of MyStaticLib static framework is accessed during the build time by MyApp.

Next open MyApp project in Xcode. Open project configuration settings (the one that says MyApp with a blue icon to its left on the project navigator). Select MyApp under targets in the editor area. Then select Build Settings tab. Search for Import Paths which lives under Swift Compiler — Search Paths. Change the value to $(PROJECT_DIR)/MyStaticLib.framework.

Setting import paths

Next from menu select Product > Build. 🎉 MyApp can now successfully consume a static framework. You can now build and run MyApp with MyStaticLib for both simulators and devices!

Summary

In this post we have learnt:

  • why one should distribute static libraries compiled
  • how to build static libraries through Xcode
  • how to consume compiled static libraries
  • what static frameworks are
  • why one should distribute static libraries as static frameworks
  • how to build static frameworks
  • how to consume static frameworks

Final notes

In this post we have learnt why one should distribute static libraries compiled. We have also learnt how to create static frameworks out of static libraries. These offer better integration experience than compiled static libraries by themselves. However compiled static libraries are more complex to integrate with than compiled dynamic frameworks. Why you ask? There is a lot to cover in that comparison. More on that later.

You can find the full source code to this post here.

Stay tuned for more on iOS development! Follow me on Twitter or Medium.

--

--

Anurag Ajwani
Onfido Product and Tech

Senior iOS Engineer at Travelperk. 7+ years experience with iOS and Swift. Blogging my knowledge and experience.