Swift framework development for binary distribution with CocoaPods

Swift Framework with Objective-C

Having previously developed some Objective-C based frameworks, I set out to explore doing the same with Swift and be able to deliver the framework as a Pod.

Creating a a Swift Framework is pretty straightforward and there are some great references on the subject.  One that I found very helpful was Creating and Distributing iOS Frameworks

I wanted to include some Objective-C based CocoaPods in my Swift Framework and quickly ran into an issue – you can’t use bridging headers with framework targets.

I did find a workaround which was interesting.  An answer in the thread Importing CommonCrypto in a Swift framework showed how to create a module that could be imported into Swift using an Aggregate target.  Using this answer as a guide, I was able to create a module for AFNetworking using the run script:

mkdir -p "${BUILT_PRODUCTS_DIR}/AFNetworkingModuleMap"
cat <<EOF > "${BUILT_PRODUCTS_DIR}/AFNetworkingModuleMap/module.modulemap"
module AFNetworking [system] {
header "${PODS_ROOT}/Headers/Public/AFNetworking/AFNetworking.h"
export *
}
EOF

This was interesting, but not the right approach.

use_frameworks!

When using CocoaPods, the correct way to include Objective-C based Pods in a Swift Framework is to use the directive use_frameworks! both in the Podfile that is used in the creation of the framework and in the host app Podfile that consumes the framework.

I first found a reference to this directive in this answer and then in the official CocoaPods blog post CocoaPods 0.36 – Framework and Swift Support which indicates that the directive is necessary if you depend on a pod which includes Swift source code.

Binary distribution

The same tutorial mentioned earlier, Creating and Distributing iOS Frameworks, also includes a nice section on preparing a framework for distribution as a Pod using a Podspec.  The description in the article serves as a great reference for a source distribution, but distributing a binary (source hidden) framework is a different beast.

I found a very nice post on how to create a binary distribution – Publish a Universal Binary iOS Framework in Swift using CocoaPods by Elad Neva.

When creating a podspec  for a binary distribution, you basically replace the attributes source_files and resources with vendored_frameworks as described in the article.  With a vendored framework (binary), the framework and license file is zipped up, stored on a server, and referenced via the source attribute.

One update to the article that I noted to Elad in a comment there – I found that I needed to reference the binary framework from a Podfile using  :podspec as in pod ‘MySDK’, :podspec => ‘/Code/mysdk-pod/’

During initial development, I used Dropbox to store the binary framework since I could easily grab a link via Finder (I believe you need a paid Dropbox subscription for direct download capability).   Note: After getting a link such as https://www.dropbox.com/…/MyBinaryFramework.framework.zip?dl=0  you need to change www to dl and remove the query arg as in https://dl.dropbox.com/…/MyBinaryFramework.framework.zip

Multiple/Dependent frameworks

Elad’s article on binary distribution almost solved all of my problems.

I attempted to use multiple frameworks where one framework that I was distributing depended on another.

My dependent framework  was called UtilsFramework and the podspec for this framework was in the same directory as the primary framework (SwiftFramework).   In the podspec for SwiftFramework I attempted to add a dependency such as:

s.dependency ‘UtilsFramework’, :podspec => ‘.’

This is not supported as in mentioned in CocoaPods issue https://github.com/CocoaPods/CocoaPods/issues/3276

The workaround as mentioned in that issue thread is to use a private repo.  We can host the podspecs for these dependent frameworks (primary framework podspec also if you wish) on a service such as GitHub.

Private Repo

The CocoaPods documentation includes a page on setting up Private Pods.

Using a private repo that is hosted on GitHub allows me to specify a dependent framework in the podspec of the primary framework such as:

s.dependency ‘UtilsFramework’

Binary Dependent framework using PODS

My initial thinking was to develop the multiple frameworks using a source distribution via Pods, and only occasionally test the binary distribution.

This meant using the following in the podspecs for SwiftFramework and UtilsFramework.

s.source = { :path => “.” }

The problem here is that :path does not pass linting and this is expected behavior as I found in Possible error in the Podspec Syntax Reference.    This means that I can’t push to a private repo with the source specified using :path

The solution is to use a binary vendored framework for any associated podspecs that must be pushed to a private repo (dependent frameworks). :http is valid for linting whereas :path is not.

To make this easier during development, I decided to automate the process of creating a zipped binary framework and serve up the framework locally using the instance of Apache running locally on my Mac.

Elad’s article on binary frameworks included a reference to a gist he published for creating a Universal framework (device and simulator slices).  I added the following to the end of that script to copy the zipped framework to my local web directory:


# Zip up the framework & license for use by UtilsFramework.podspec
( cd "${PROJECT_DIR}" && zip -r - LICENSE UtilsFramework.framework ) > /Users/scarter/Web/Utils.zip

I can now refer to the UtilsFramework binary in the podspec for UtilsFramework with:

s.source = { :http => 'http://127.0.0.1/Utils.zip' }

Using the Private Repo

The Podfile used with the creation of my frameworks (SwiftFramework/UtilsFramework) as well as the Podfile used by the host app now includes the following source directive which allows my private podspecs to be found:

source 'https://github.com/scottcarter/Specs.git'

Development in practice

After flushing out all the issues with delivering binary frameworks via CocoaPods, I set out to create an environment methodology that I could use for development.

Goals

  • Create a standalone sample app that pulled in my frameworks via binary distribution using CocoaPods.
  • Provide an environment that allowed for easy development of my frameworks (no CocoaPods for my own framework dependencies).
  • Re-use the sample app code during development and testing of the frameworks.

Standalone App Workspace

The standalone app was setup with a simple project/workspace that pulled in the binary frameworks and other third-party code I might use via a Podfile.

Framework Workspace

I created a separate project/workspace for framework development.  Each framework had its own target.

I also created an app target.   With the app target I removed all of the boilerplate files and then added a folder reference to the standalone app code.   After doing this I needed to go to the Target for the app, and under the General tab choose an Info.plist file.  There was some additional Build Phases setup as explained in the next section on working with folder references.

Since I wasn’t using CocoaPods for my own framework dependencies in this environment, I did the following:

  • Added UtilsFramework to Link Binary With Libraries section for SwiftFramework.
  • Added both SwiftFramework and UtilsFramework to Embed Frameworks and Link Binary With Libraries sections for app target.

Working with folder references

A folder reference allows me to work on one code base in two different workspaces, but is not without some overhead.

As explained in the answer to the post Can Xcode Use “Folder References” for Code? folder references don’t necessarily work as expected.  In particular, you will need to manually update Compile Sources and Copy Bundle Resources sections in Build Phases.

For me, the advantage of being able to use the same source code in these 2 workspaces was worth the tradeoff.

 

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s