Friday, 18 September 2009

Code: Creating a framework for the iPhone

Summary
This article explains how to build your own framework for Apple's iPhone OS.

The problem

Apple's Xcode development environment does not let programmers create their own framework for use in iPhone OS applications. This has caused many iPhone developers great frustration, although the restriction is for fairly sensible reasons.

So why the restriction?

A framework usually contains a dynamically loaded shared library (and the associated header files to be able to access its facilities). iPhone OS keeps applications very separate from one another, and so there is no concept of a user-created dynamic library shared between applications. There is no central library install point accessible to the developer. Indeed, managing such a software pool would be rather complex on iPhone-like devices, and preventing developers from installing their own shared frameworks neatly sidesteps a whole world of painful shared library compatibility issues, and well as simplifying the application uninstall process.

Its one, fairly final, way to avoid DLL hell!

All applications may link to the blessed, system-provided frameworks. The only other libraries they may use must be standard static libraries, linked directly to the application itself.

Those of us who'd like to supply functionality to other users in library form are left at somewhat of a disadvantage, though. Most developers are used to the simplicity of dragging a framework into their application target in Xcode, and not worrying about header paths or link issues.

It's nowhere near as neat to have to provide a static library and a set of associated header files in a flat directory. This requires your clients to work out the installation in their application for themselves. It's not hard, but it is tedious. You've also got to ship a library version for each platform the developer will need (at the very least, an arm code library for use on the iPhone OS device itself, and an i386 build for them to use in the iPhone simulator).

It's clumsy.

But fear not, there is a way...


How to build your own framework

I've worked out how to create a usable Framework that you can ship to other iPhone OS application writers. You can ship libraries that are easy to incorporate into other projects, and can exploit the standard framework versioning facilities.

There is one caveat: the framework will not be a standard shared library, it will provide a statically linked library. But the application writer need not be concerned about this issue. As far as they're concerned everything will just work. We are using Apple machines, after all.

Here's how to do it:

1. Structure your framework's header files.

Let's say your library is called "MyLib". Structure your project with a
top-level directory called "Include", and inside that make a "MyLib" subdirectory. Put all your public header files in there.

To be idiomatic, you'll want to create an umbrella header file "Include/MyLib/MyLib.h" that includes all the other headers for the user's convenience.

Set up your Xcode project "Header Search Paths" build parameter to "Include".
Now your source files can happily #import <mylib/mylib.h> in the same way they'd use any other framework. Everything will include properly.

2. Put your source elsewhere

I have a "Source" directory containing subdirectories "Source/MyLib" and "Source/Tests". You can put your implementation files (and private header files) wherever you want. Just, obviously, not in the Include directory!

3. Create a static library target

Create an iPhone OS static library target that builds all your library sources. Call it MyLib, and by default it will create a static library called libMyLib.a.

4. Create the framework plist file

Create a plist file that will be placed inside your framework, describing it.

I keep mine in Resources/Framework.plist. It's a peice of XML joy that should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>MyLib</string>
<key>CFBundleIdentifier</key>
<string>com.MyLovelyDomain.MyLib</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
</dict>
</plist>
5. The magic part... build your framework by hand

Create a shell script to build your framework. I have a "Scripts" directory that contains it, because I like to keep things neat like that.

The first line is the canonical hashbang:
#!/bin/bash
There are two parts to this file...

5a. Build all the configurations that you need your framework to support

This must be at least armv6 for the device, and x386 for the simulator. You'll want these to be Release configuration libraries.
xcodebuild -configuration Release -target "MyLib" -sdk iphoneos3.0
xcodebuild -configuration Release -target "MyLib" -sdk iphonesimulator3.0

So that's our libraries built. Now...

5b. Piece it all together

With a little understanding of the canonical structure of a framework directory, our ability to write a plist, and the knowledge that putting a static library in the framework instead of a dynamic library works fine, you can create your framework like this (apologies that blogger has kinda killed the formatting here):

# Define these to suit your nefarious purposes
FRAMEWORK_NAME=MyLib
FRAMEWORK_VERSION=A
FRAMEWORK_CURRENT_VERSION=1
FRAMEWORK_COMPATIBILITY_VERSION=1
BUILD_TYPE=Release

# Where we'll put the build framework. The script presumes we're in the
# project root directory
FRAMEWORK_BUILD_PATH="build/Framework"

# Clean any existing framework that might be there already
echo "Framework: Cleaning framework..."
[ -d "$FRAMEWORK_BUILD_PATH" ] && rm -rf "$FRAMEWORK_BUILD_PATH"

# The full name of the framework we'll build
FRAMEWORK_DIR=$FRAMEWORK_BUILD_PATH/$FRAMEWORK_NAME.framework

echo "Framework: Setting up directories..."
mkdir -p $FRAMEWORK_DIR
mkdir -p $FRAMEWORK_DIR/Versions
mkdir -p $FRAMEWORK_DIR/Versions/$FRAMEWORK_VERSION
mkdir -p $FRAMEWORK_DIR/Versions/$FRAMEWORK_VERSION/Resources
mkdir -p $FRAMEWORK_DIR/Versions/$FRAMEWORK_VERSION/Headers

echo "Framework: Creating symlinks..."
ln -s $FRAMEWORK_VERSION $FRAMEWORK_DIR/Versions/Current
ln -s Versions/Current/Headers $FRAMEWORK_DIR/Headers
ln -s Versions/Current/Resources $FRAMEWORK_DIR/Resources
ln -s Versions/Current/$FRAMEWORK_NAME $FRAMEWORK_DIR/$FRAMEWORK_NAME

# Check this is what your static libraries are called
FRAMEWORK_INPUT_ARM_FILES="build/$BUILD_TYPE-iphoneos/libMyLib.a"
FRAMEWORK_INPUT_I386_FILES="build/$BUILD_TYPE-iphonesimulator/libMyLib.a"

# The trick for creating a fully usable library is to use lipo to glue the
# different library versions together into one file. When an application is linked
# to this library, the linker will extract the appropriate platform version and
# use that.
# The library file is given the same name as the framework with no .a extension.
echo "Framework: Creating library..."
lipo \
-create \
-arch armv6 "$FRAMEWORK_INPUT_ARM_FILES" \
-arch i386 "$FRAMEWORK_INPUT_I386_FILES" \
-o "$FRAMEWORK_DIR/Versions/Current/$FRAMEWORK_NAME"

# Now copy the final asserts over: your library header files and a plist file
echo "Framework: Copying assets into current version..."
cp Include/$FRAMEWORK_NAME/* $FRAMEWORK_DIR/Headers/
cp Resources/Framework.plist $FRAMEWORK_DIR/Resources/Info.plist
That's it. Run that script and you'll create a framework in "build/Framework". In there is MyLib.framework. This directory can be shipped to your external application developers. They can incorporate it into their iPhone OS applications like any other framework.

Congratulations, you are now a hero.


Other remarks

I have presented here the most basic structure of a shell file. My production version includes more robust error handling, and other facilties that are relevant to my particular project.

I also have a build script that automatically creates documentation for the framework that I can ship with it. Indeed, I have a release script that applies versioning information to the project, build the libraries, creates a framework, assembles the documentation, compiles release notes and packages the whole thing in a pretty DMG. But that's another story.

If calling scripts from the command line scares you, you may chose to make a "Run Script Build Phase" in your Xcode project to call your framework script. Then you can create a framework without having to creep to the command line continually.

In summary, the final file layout of my project looks like this:
....Include/
........MyLib/
............MyLib.h
............IncludeFile1.h
............IncludeFile2.h
....Source/
........MyLib/
............PrivateHeader.h
............ImplFile1.m
............ImplFile2.m
........Tests
............MyLibTest1.m
............MyLibTest2.m
....Scripts/
........framework.sh

I hope you have found this tutorial useful. Let me know what frameworks you manage to build.

No comments: