Apple's iPhone SDK doesn't allow you to create a Framework. Pete Goodliffe explains how to build one manually.
Apple's iPhone SDK and the Xcode development environment are a powerful and very easy way to develop incredible mobile applications. The facilities they provide (in ease development, debugging, and the rich Cocoa Touch libraries) far exceed what was available on desktop platforms only a few years ago.
However, there is still a natural bias to simple stand-alone application development - you cannot build your own shared libraries to reduce memory footprint; you can only create applications or simple static libraries. There are some good reasons for this, security and ease of installation being two of the most obvious.
However, seasoned Apple developers are not used to simple static libraries; they are used to Apple's OS X Frameworks (essentially a shared library on steroids, see the sidebar) which are a very convenient method for code sharing and reuse.
Despite the restriction, with a little elbow work and some simple scriptery it is possible to enjoy most of the benefits of a Framework on the iPhone platform. This article explains how to build your own framework for Apple's iPhone OS. I presume a level of familiarity with static and shared libraries. You'll also need to understand bash shell scripting. Understanding the rudiments of Apple development is useful, particularly the Xcode development environment.
What is a framework? |
Although the Apple build technologies for Mac OS and iPhone OS are essentially Unix-like (using the gcc compiler and binutils linker) Apple have applied a number of tucks and tweaks. The addition of Frameworks is one such addition; support for Frameworks has been added into the Apple versions of gcc and binutils. Frameworks are hierarchical directory structures grouping related but separate items, for example: dynamic libraries, header files, user interface assets and documentation. They provide an internal versioning facility (defined by the directory hierarchy). The OS provides support for loading items from a framework directory, ensuring only one copy is in memory at any time. Almost all Mac OS and iPhone SDK services are packaged as Frameworks and most third party Mac OS libraries also ship as frameworks. Using a framework in Xcode is as simple as a drag-and-drop operation; header paths and linkage are looked after for you automatically. Frameworks (and the "Bundle" directory format they are based on) date back to the NextStep platform, which was acquired by Apple and used as the foundation for OS X. More on information frameworks can be found at Apple's Developer Connection site [ Apple ]. |
The problem
Apple's Xcode development environment does not let programmers create their own framework for use in iPhone OS applications. As you can see from Figure 1, when targeting the iPhone you can only create an Application , a Static Library , or a Unit Test Bundle (I have to give Apple credit for including the last item, and for its integrated support of unit tests in the Xcode IDE. But that's a different article). This has caused many iPhone developers great frustration, although the restriction is for fairly sensible reasons (Figure 1).
Figure 1 |
So why this restriction?
A framework usually contains a dynamically loaded shared library, and the associated header files a client application requires 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 (the OS hides the file system from developer and user alike). Preventing developers from installing their own shared frameworks neatly sidesteps a whole world of painful shared library compatibility issues, and simplifies the application uninstall process.
It's one, fairly final, way to avoid DLL hell [ DLL ]!
All applications may link to the blessed, system-provided frameworks 1 . The only other libraries they may use must be standard static libraries, linked directly to the application itself.
For most simple application developers this situation is perfectly fine. However, those of us who'd like to supply functionality to other users in library form are left at somewhat of a disadvantage. Most Apple-savvy application developers are used to the simplicity of dragging a framework bundle onto their application target in Xcode, and not worrying about header paths or link issues. #include magically works, and the linkage issues are sorted out under the covers.
As a library provider, it is nowhere near as neat to have to provide a static library and a set of associated header files in a separate flat directory. Doing this requires your clients to work out how to integrate your library in their application by hand. And it'll make integrating a new version of your library into the application more work. Granted, it's not hard (for people who know what they're doing), but it is tedious. That's not the Apple Way , is it?!
When shipping a static library, you will also have 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).
As you can see, static library usage on the iPhone is clumsy. But fear not, there is a way...
How to build your own framework
I have 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: because of iPhone OS limitations the framework will not be a shared library; it will only 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 as if they were using a standard OS framework.
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. See Figure 2.
Figure 2 |
Set up your Xcode project Header Search Paths build parameter to include Include (note, do not include the MyLib subdirectory) as in Figure 3.
Figure 3 |
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 files elsewhere
I create 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 this target 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. Plist files are Property Lists , used to store settings about an object (in this case the details about this framework).
I keep my plist file in Resources/Framework.plist. It's a piece of XML joy that should look like Listing 1.
<?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> |
Listing 1 |
5. Construct the framework by hand
Now this is where the real magic happens. Create a shell script to build your framework. I have a Scripts top-level directory that contains it, because I like to keep things neat like that. Make sure your script file is executable 2 .
The first line is the canonical hashbang [ Shebang ]:
#!/bin/bash
Following this there are two parts to the file...
5a. Build all the configurations that you need your framework to support
There must be at least a build for armv6 for the iPhone device itself, and an x386 build for the iPhone simulator. Application developers will require both of these to be able to work. 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. That was the simple bit. 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 using the script in Listing 2. The comments in the listing describe exactly what's going on.
# 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. Xcode builds in "build" by default 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" # This is the full name of the framework we'll # build FRAMEWORK_DIR=$FRAMEWORK_BUILD_PATH/$FRAMEWORK_NAME.framework # Build the canonical Framework bundle directory # structure 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 that 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 assets over: your library # header files and the 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 |
Listing 2 |
In summary, this script:
- Cleans up any existing Framework (this is cleaner than simply building over the top of anything that may be already there)
- Creates the canonical directory structure for a Framework.
- Creates a single library file that supports all necessary platforms using the lipo tool.
- Copy header files into the Headers directory.
- Copy the plist file into the Framework.
This script generates a fully usable Framework bundle in the build/Framework directory. It is called MyLib.framework . This bundle can be shipped to your external application developers. They can incorporate it into their iPhone OS applications like any other framework.
Other remarks
I have presented here the most basic structure of a shell file. My production version includes more robust error handling, and other steps 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, builds the libraries, creates a framework, assembles the documentation, compiles release notes and packages the whole thing in a pretty DMG.
If calling scripts from the command line scares you, you may choose to make a 'Run Script Build Phase' in your Xcode project to call your framework script from there. 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 Figure 4.
Figure 4 |
I hope you have found this tutorial useful.
References
[Apple] 'What are Frameworks?' Apple Developer Connection. http://developer.apple.com/mac/library/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WhatAreFrameworks.html
[DLL] 'DLL Hell', Wikipedia. http://en.wikipedia.org/wiki/DLL_hell
[Shebang] 'Shebang (Unix)', Wikipedia. http://en.wikipedia.org/wiki/Shebang_%28Unix%29
1 Indeed, they may only use the public, documented methods of the system frameworks. Applications that discover and use undocumented APIs will not be allowed onto Apple's carefully policed App Store. This seems draconian, but again, is for fairly obvious reasons.