Getting started with custom Swift frameworks

This is a broad overview.  I’m using Xcode 6.3.1.

tl;dr

Alternatives to creating a framework to be referenced in another project:

  1. add a framework target to a referencing project
  2. add a framework product to a referencing project, from a separate framework project
  3. add a framework project to a referencing project, from a separate framework project

Alternative 1 works best: you can archive the referencing project.  But the source files for the framework are all visible in the referencing project, even if they are also visible in a separate project for the framework, and regardless where the source files live.

Alternative 2:  this doesn’t seem to work.  You can’t archive the referencing project, and every time you build it for a different platform, you need to refresh the framework to one built for that platform.

Alternative 3: didn’t seem to work at all.

Disambiguating

‘Framework’ is Apple’s word for library.

‘Custom’ means: not provided by Apple.

A Swift framework is a ‘module’ that you ‘import.’

A ‘pure Swift framework’ is written entirely in Swift, without any embedded Objective-C or other languages.

A library has an API of methods you can call.

Why frameworks?

Frameworks let you reuse.

Two use cases:

– someone else has written and shared a framework

– you want to move your code into frameworks that you can share between projects

Basics of building a framework

Xcode has a template for framework projects.  See Build your own Cocoa Touch Frameworks, in pure Swift

Code the project with the ‘public’ access modifier on all methods you want exposed in the framework’s API.  Swift docs about Access Control.

Build the project for ‘iOS Device’.  (If you build it for the simulator, the Product>framework will be red in the Project Navigator.)

Building a downloaded framework

Download the source and hope the Xcode project is configured properly to build a framework product.

Start Xcode and choose Window> Welcome to Xcode.  Expect a window to open.  Choose “Open another project..”.  Expect a Finder window to open. Navigate to the .xcodeproj file inside the source you downloaded.  Choose OK.  Expect the project to open in Xcode.  Build as above (for iOS Device.)  Expect the build to succeed and for Products>.framework to turn black in the Project Navigator pane.

Using the framework in another project

Start another project (say an iOS app or game.)

Use the Finder to reference your project to your framework:

  • open the framework project and right click on the framework product.  Expect a pop-up menu.
  • choose “Show in Finder”.  Expect a Finder window to open, with your framework selected.
  • leave the Finder window open
  • in Xcode, close the framework project and open the referencing project
  • drag the selected framework from the Finder window into the Project Navigator pane of the referencing project.  Expect a dialog to open giving choices of copying, creating groups, etc.
  • choose OK (or change the options, I don’t understand them.)  Expect for the framework to appear in the Project Navigator pane (e.g. a lunchpail icon followed by SwiftState.framework.)

Now in your code you can insert ‘import “.  Expect Xcode to compile the import without errors.

Then you can insert code in your referencing project to call the API methods of the framework.

At this point, XCode complained that it could not find constructs from the framework.  ( Using the SwiftState framework from github, it could not find the StateEvent protocol defined by the framework. ) I changed the active scheme (in the upper left) from the “iPhone 6” simulator to “iOS Device” to solve that problem.  Apparently, the framework is built for a particular architecture (ARM architecture of an iPhone) and building for the simulator builds for the i86 architecture of the simulator?  I am not sure about this.

(This is far as I’ve gotten.)

Compiled libraries

Traditionally, you can obtain compiled libraries:

  • without source code
  • compiled for a certain architecture
  • with a header (.h or other) file defining the API

I am not sure you can do that for Swift frameworks.  So this blog might be limited to the case where you have the source (and often an Xcode project file .xcodeproj).

Dependencies

Libraries and frameworks can import, and thereby depend on, other libraries.

With libraries, you can get into ‘dependency hell’, where you don’t have the dependent libraries, or the graph of dependencies has version conflicts, etc.  If you are building all your custom frameworks yourself, this might not be a problem, unless you find old frameworks using old Swift constructs.

CocoaPods might help in this regard.

FAQ / Gotchas / Advice

A recounting of problems I encountered:

Background

My project was getting too large.  I wanted to move some classes into a framework to reduce coupling, and so I could share the framework.  The framework was code that I wished someone else had shared.  Code that was general, not the essence of my application. In my case a scoreboard for a Swift SpriteKit game.

My first advice is: in the original project, refactor the code to reduce coupling.  If you do that later, the fact that the code is now in two projects complicates the process (see below, Changing a Framework.)  In my case, I had several classes, like Scoreboard, HighScore, Players, etc. I reduced the coupling by making the Scoreboard own instances of the other classes, and making the Scoreboard delegate certain methods to its owned objects.

In other words, design a clean API for the framework.  Then implement that in the original project.  Only then move the code into a framework.

Changing a Framework

When I discovered problems in the API, and tried to make changes to the framework project, the changes were not reflected in the referencing project.  For example after adding a foo method to the API, I got “Scoreboard has no foo method.”

To verify it is a problem, I selected “ScoreBoard” in an “import Scoreboard” code, right clicked, and chose “Jump to Definition.” Then Xcode showed my a “Scoreboard.h” file, which defines the API. This file apparently is generated by the Swift compiler and is nowhere visible in the file system.  That never changed even after I rebuilt the framework project.   Even Cleaning the Build Directory and deleting the ModuleCache on both projects did not seem to help.  Also, the “file modified” date on …referencing project path…/Scoreboard.framework/Modules/ScoreBoard.swiftmodule/arm.swiftmodule  never changed.  Not sure that is related, but it seems likely.

I was unable to get it to refresh.  I ended up having to delete and recreate the framework project (not the source files, just the project.)

This problem seems to be restricted to changes to the API (to the .h file.)  When I made code changes to the framework that did not change the API, the changes did seem to propagate to the referencing project.

Targets in the Framework

In my framework project, I only have one target and I build for iOSDevice.  (If you ship for the OS X platform, you need another target?)

But you can build for different platforms (iOS Device or iOS Simulator.)  If you select the Product>foo.framework, and choose “Show in Finder” you will see that the framework is built for two platforms, where the parent directory is either:

  • Debug-iphoneos
  • Debug-iphonesimulator

Alternatives:

  1. One blog suggests building the framework as a Universal Binary.
  2. instead of adding the framework to your project, add the framework’s project to our project
  3. manually delete and add the framework built for a specific platform to your referencing project when you change platforms

Alternative 1:  I looked at it but it seemed rather more involved and low level than I wanted.

Alternative 2: I haven’t tried this but I suspect it is the easiest way to go.

Alternative 3:…

When you delete the framework from your referencing project (delete the reference) you must then:

  • add the framework back, but choosing from the parent directory for the platform (e.g. Debug-iphonesimulator)
  • add the framework again to General>Embedded Binaries
  • insure that the value (a list of paths) for Build Settings>SearchPaths>Framework Search Paths does not still include a path to the directory of the previous platform (delete that path if it does)

I imagine this alternative will get tedious if you switch back and forth between the iOS simulator and real devices often.

Minimum iOS Version

When I created the framework project, it was configured for a minimum iOS version 8.3.  My referencing project was configured for 8.1, and in my first build, Xcode complained.

So in the framework under the project target ScoreBoard>Build Settings>Deployment>iOS Deployment Target, I chose iOS 8.1.

Embedding a Binary

After I built and ran for a real iOS device, I got “dyld: Library not loaded: @rpath/Score.framework/Score.”  To fix that, in the referencing project, under the target, under General>Embedded Binaries”, I clicked the + button.  Expect a chooser dialog showing the contents of your project, which should include system frameworks and your custom framework.  Choose your custom framework, e.g. Scoreboard.framework.

When I did this, the framework also (automatically) appeared under “Linked Frameworks and Libraries” (but I had already done that?) so I deleted the duplicate.

Also when I did this, the framework automatically appeared under “Build Phases>Embed Frameworks”

The explanation is: system frameworks will be found on the device (not in your bundle).  Custom frameworks must be embedded.  All frameworks are linked in, but since they are dynamically linked, the final step of linking is done later (when the app is installed or run?)  Custom frameworks must be embedded in the bundle.

Archiving

When I added a framework product to a referencing project, then tried to archive a project target in the referencing project, it failed (the framework wasn’t visible.)  In other words, it only works for building in debug mode.

So I resorted to adding a target for the framework in the referencing project (instead of adding the framework product.)  When you add a target that is a “Cocoa Touch Framework”, one of the options is “Embed in Application”.  I chose to embed it in the main application target of the project.  Expect:

  • a folder e.g. “Score” to appear in the Project Navigator pane
  • the “Score.framework” to appear in the “General>Embedded Binaries” of the main app target.

Then in the “Score” folder, I added all the source files from their location in my “Score” framework project.

So I am left with a separate “Score” framework project, where I do source code control, and which I can share.  But the source is also added to the referencing project (in a separate “Source” group/folder).  Which seems pointless: the source code was originally in the referencing project, and the whole point of the exercise was to move it out.

Advertisements

One thought on “Getting started with custom Swift frameworks

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