Modular iOS

How to distribute compiled static frameworks via Cocoapods

Anurag Ajwani
7 min readJun 22, 2020
Photo by Aaron Huber on Unsplash

Great software also has a great installation experience. If the software is too hard to install few will use it.

Installing and integrating with compiled iOS static frameworks has some caveats. How does an integrator know when you release a new version of your static framework — especially one with bug fixes? What happens when newer versions of your compiled static frameworks require changes in the integrators build configuration? When the installation changes does that become a breaking change too?

There is a way to simplify the installation and maintenance experience for integrators of your compiled static framework. There are tools that help integrators manage 3rd party frameworks and libraries in their apps. These tools help integrators install new versions of their dependencies effortlessly with a run of a single command. These tools are called dependency managers. The most popular for iOS is Cocoapods.

For this post I assume you are familiar with iOS frameworks, static libraries and static frameworks. I also assume you have basic Swift and iOS app development knowledge. Addtionally you must also have knowledge of Git version control.

I have used Swift 5.2.4 and Xcode 11.5 whilst writing this post.

Why use Cocoapods?

Let’s say an integrator of your compiled static framework finds a bug — let’s say 1.0.0. They report it to you. You fix it. You release a new version — 1.0.1. You let the integrator know the new version is out. The integrator downloads and installs the new version.

Let’s take a look how the same scenario would work with Cocoapods. The integrator declares to Cocoapods that they wish to use any version of 1.x.x. On installation Cocoapods will install the latest version — whether it’s 1.0.1 or 1.1.0. You no longer need to reach to every integrator to upgrade.

Additionally when the integrator uses your static framework they must keep a copy in their repository so that all of developers have a copy. Otherwise each developer must install it themselves. Not only for your static framework but all that are not managed by a dependency manager.

With Cocoapods, however, the developer must only run a single command. Cocoapods will take care of the rest: download, install and configure the framework into their app.

How does Cocoapods work?

In simple terms a pod (a framework or library) publisher to Cocoapods has to publish a file known as Cocoapods specification or podspec to a repository that holds these. The specification holds information on how to install and configure the pod.

When the integrator decides to install your pod they declare the name of pod and optionally the version (if not specified then it will take the specification for the latest version) in its list of pods to be installed. Cocoapods then fetches the specification from the respository and installs the pod according to the specification.

How to distribute compiled static frameworks through Cocoapods

We’ll start off by downloading an exiting project containing an existing static library project. I have included a script in the project that wraps the static library in a static framework.The download also includes an iOS app which we’ll use in the next section to consume the distributed static framework through Cocoapods.

Before we get staterted open the Terminal app. All of the steps in this section will require it.

The steps we’ll take:

  1. Download the starter project
  2. Build the static framework
  3. Store the static framework in a local git repository
  4. Install Cocoapods
  5. Create Cocoapods specification repository
  6. Create specification file
  7. Publish static framework specification to the specification repository

1. Download the starter project

Let’s start by downloading the starter project. We’ll be using Terminal to carry out this task. Execute the following commands:

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

2. Build the static framework

Let’s build the static framework that we’ll publish later. There is a script already included in the project to do so. Execute the following command:

sh build_static_framework.sh

The command above will output a directory named MyStaticLib.framework. This will be the artefact to publish to our integrators.

3. Store the static framework in a local git repository

Next we’ll create a git repository to store and track versions of the compiled static framework. This will be the place where Cocoapods will fetch the framework from. Execute the following commands:

cd ~
mkdir MyFrameworkDistribution.git
cd MyFrameworkDistribution.git
git init --bare
git clone ~/MyFrameworkDistribution.git ~/MyFrameworkDistribution
cd ~/MyFrameworkDistribution
touch README.md
git add README.md
git commit -m "Initial commit"
git push origin -u master

Next we’ll copy the generated static framework into the repository, commit and tag it. Execute the following commands:

cp -r ~/compiled_swift_static_lib-completed/MyStaticLib/MyStaticLib.framework ~/MyFrameworkDistribution
git add MyStaticLib.framework
git commit -m "Added MyStaticLib.framework"
git tag -a 1.0.0 -m "Version 1.0.0"
git push origin master
git push origin --tags

Above we have tagged the commit containing MyStaticLib.framework with 1.0.0. This is how Cocoapods will find the right version when the integrator specifies that they want to install version 1.0.0 of our static framework.

4. Install Cocoapods

Before we can publish the specification we need to install Cocoapods. If you already have Cococapods installed feel free to skip this step. Otherwise execute the following command:

sudo gem install cocoapods

5. Create Cocoapods specification repository

Next we’ll create a local git repository to hold Cocoapods specification files. We’ll also tell Cocoapods to register the specification repository in its list. Execute the following commands:

cd ~
mkdir MySpecs.git
cd MySpecs.git
git init --bare
git clone ~/MySpecs.git ~/MySpecs
cd ~/MySpecs
touch README.md
git add README.md
git commit -m "Initial commit"
git push origin -u master
pod repo add my-specs ~/MySpecs.git

6. Create specification file

Next we’ll create a specification file for the framework. The specification must tell Cocoapods where to fetch the framework from and how to configure it. Execute the following command:

cat > ~/compiled_swift_static_lib-completed/MyStaticLib/MyStaticLib.podspec <<-EOF
Pod::Spec.new do |s|
s.name = "MyStaticLib"
s.version = "1.0.0"
s.summary = "A brief description of MyStaticLib project."
s.description = <<-DESC
An extended description of MyStaticLib project.
DESC
s.homepage = "http://your.homepage/here"
s.license = { :type => 'Copyright', :text => <<-LICENSE
Copyright 2020
Permission is granted to...
LICENSE
}
s.author = { "$(git config user.name)" => "$(git config user.email)" }
s.source = { :git => "$HOME/MyFrameworkDistribution.git", :tag => "#{s.version}" }
s.vendored_frameworks = "MyStaticLib.framework"
s.platform = :ios
s.swift_version = "5.2"
s.ios.deployment_target = '12.0'
s.user_target_xcconfig = {
'SWIFT_INCLUDE_PATHS' => '"\$(PODS_ROOT)/MyStaticLib/MyStaticLib.framework"'
}
end
EOF

Note s.source. This property tells Cocoapods where to fetch the framework from. Also note:

s.user_target_xcconfig = {
'SWIFT_INCLUDE_PATHS' => '"\$(PODS_ROOT)/MyStaticLib/MyStaticLib.framework"'
}

The property above tells Cocoapods to configure SWIFT_INCLUDE_PATHS build setting of the integrator to include the path to the framework’s .swiftmodule directory. The directory contains the public interface for the framework. These files are required for the compiler to know what code the integrator can access from the framework.

7. Publish static framework specification to the specification repository

Finally we need to publish the specification to the specification repository. Execute the following command:

pod repo push my-specs ~/compiled_swift_static_lib-completed/MyStaticLib/MyStaticLib.podspec

That’s it. The pod is ready to be consumed.

Consuming compiled static frameworks distributed through Cocoapods

In this section we’ll be installing and consuming the static framework through Cocoapods. We’ll be making use of an already existing app that is included in the starter project.

The steps we’ll take:

  1. Create Podfile
  2. Install dependencies
  3. Consume the static framework

1. Create Podfile

Before we can install any dependencies to the app we must create a file named Podfile. A Podfile holds information on the dependencies Cocoapods must install for a target in the Xcode project.

Let’s create the Podifle. Execute the following command:

cd ~/compiled_swift_static_lib-completed/MyApp
cat > Podfile <<-EOF
target 'MyApp' do
use_frameworks!
pod 'MyStaticLib', '1.0.0', :source => "$HOME/MySpecs.git"
end
EOF

Above we have declared MyStaticLib as dependency of MyApp target. Also we specified that we’ll be using version 1.0.0 and where to find the specification file (:source).

2. Install dependencies

Next we have to tell Cococapods to install dependencies declared in the targets specified. Execute the following command:

pod install

3. Consume the static framework

In this step we’ll be adding some code to the project. First let’s open the project in Xcode. Execute the following command:

open -a Xcode MyApp.xcworkspace

Within Xcode open ViewController.swift. First let’s import the framework. Under import UIKit add the following line:

import MyStaticLib

Next let’s consume the code included in the framework. Add the following code to viewDidLoad function:

functionA()

functionA lives inside the static framework and prints hello to console. Run the app and see the message printed to console! 🎉

console output

Summary

In this post we learnt:

  • how to distribute compiled static frameworks through Cocoapods
  • how to consume compiled static frameworks using Cocoapods

Final Notes

Compiled frameworks are a bit of hack. It allows us to build a single universal iOS static library artefact. However static frameworks are not supported by Apple.

Being not fully supported by Apple, the workability of static frameworks in the current form is not guaranteed. They might not work at all in the future. I have been using compiled static frameworks in the current form for many years without a change though.

That said Apple has introduced a potential solution to replace this hacky mechanism through their new module format known as XCFrameworks. I will share my findings in a future post.

Stay tuned! Follow me on Twitter or Medium.

--

--

Anurag Ajwani

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