An XCode 4 project template to build universal (arm6, arm7, and simulator) frameworks for iOS.
By Karl Stenerud
Why a Framework?
Distributing libraries in a developer-friendly manner is tricky. You need to include not only the library itself, but also any public include files, resources, scripts etc.
Apple's solution to this problem is frameworks, which are basically folders that follow a standard structure to include everything required to use a library. Unfortunately, in disallowing dynamically linked libraries in iOS, Apple also removed static iOS framework creation functionality in XCode.
Xcode is still technically capable of building frameworks for iOS, and with a little tweaking it can be re-enabled.
Kinds of Frameworks
The most common kind of framework is the dynamically linked framework. Only Apple can install these on an iOS device, so there's no point in building them.
A statically linked framework is almost the same, except it gets linked to your binary at compile time, so there's no problem using them.
A fake framework is the result of a hack upon a bundle target in Xcode and some scripting magic. It looks and behaves like a static framework, but it's implemented as a relocatable object rather than a static library.
An embedded framework is a wrapper around a static framework, designed to trick Xcode into seeing the framework's resources (images, plists, nibs, etc).
These template systems will build static frameworks, fake frameworks, and embedded framework variants of each.
Choosing Which Template System to Use
In this distribution are two template systems, each with their strengths and weaknesses. You should choose whichever one best suits your needs and constraints (or just install both).
Fake Framework
The fake framework uses the "relocatable object file" bundle hack that's been posted all over the net, which tricks Xcode into building something that mostly resembles a framework.
The binary it outputs is an object file rather than a library, and is of type "cfbundle", which Xcode doesn't like in certain circumstances.
If all you're doing is adding the finished, compiled framework to a project, this is not a problem. If, however, you try to add the fake framework's PROJECT as a dependency of another project, or as part of a workspace, Xcode will skip the framwork during the dependency phase, and will fail to link properly. You can force the framework to link using the "-force_load" flag in "Other Linker Flags" in the project that uses the framework:
-force_load "$(BUILT_PRODUCTS_DIR)/MyFramework.framework/MyFramework"
Real Framework
The real framework is real in every sense of the word. It is a true static framework made by re-introducing specifications that Apple left out of Xcode.
In order to be able to build a real framework project, you must modify Xcode's specification files, which may not be desirable, or even possible due to restrictions your organization may place upon development environments. Also, if you are releasing a project that builds a real framework, anyone who wishes to build that framework must modify their Xcode as well (via the install script in this distribution). If all you're doing is distributing the fully built framework, and not the framework's project, the end user doesn't need to modify Xcode.
I've submitted a report to Apple in the hopes that they'll update the specification files in Xcode, but that could take awhile. OpenRadar link here
Installing the Template System
To install, run the install.sh script in either the "Real Framework" or "Fake Framework" dir (or both).
Now restart Xcode and you'll see Static iOS Framework (or Fake Static iOS Framework) under Framework & Library when creating a new project.
To uninstall, run the uninstall.sh script and restart Xcode.
Creating an iOS Framework Project
-
Start a new project.
-
For the project type, choose Static iOS Framework (or Fake Static iOS Framework), which is under Framework & Library.
-
Optionally choose to include unit tests.
-
Add your classes, resources, etc with your framework as the target.
-
Any header files that need to be available to other projects must be declared public. To do so, go to Build Phases in your framework target, expand Copy Headers, then drag any header files you want to make public from the Project or Private section to the Public section.
Building your iOS Framework
-
Select the Universal_Framework scheme (any of its targets will do).
-
(optional) Set the "Run" configuration for the "Universal_Framework" scheme (Debug by default).
-
Build Universal_Framework.
-
Select your framework under "Products", show in Finder, go up one directory, and use the framework inside (your-configuration)-universal
The Universal_Framework scheme will build the framework for arm6, arm7 and simulator, and merge them together into a universal framework.
The finished product will be in your standard build location as (configuration)-universal/(your framework).framework. The "Run" build configuration in the "Universal_Framework" scheme controls which configuration to build the universal framework from.
It will also build (your framework).embeddedframework. This contains the normal framework, and also contains extra symlink references to any resources included in your project. XCode won't look inside static frameworks for resources, so if you're going to include resources, you must distribute the embeddedframework version.
Using an iOS Framework
iOS frameworks are basically the same as regular dynamic Mac OS X frameworks, except they are statically linked.
To add a framework to your project, simply drag it into your project. When including headers from your framework, remember to use angle bracket syntax rather than quotes.
For example, with framework "MyFramework":
#import <MyFramework/MyClass.h>
Troubleshooting
Headers Not Found
If Xcode can't find the header files from your framework, you've likely forgotten to make them public. See step 5 in Creating an iOS Framework Project
No Such Product Type
If someone who has not installed iOS Universal Framework in their development environment attempts to build a universal framework project (for a real framework, not a fake framework), they'll get the following error:
target specifies product type 'com.apple.product-type.framework.static',
but there's no such product type for the 'iphonesimulator' platform
Xcode requires some modification in order to be able to build true iOS static frameworks (see the diff files for details), so please install it on all development machines that will build your real static framework projects (this isn't needed for users of your framework, only for builders of the framework).
The selected run destination is not valid for this action
Sometimes Xcode gets confused and loads the wrong active settings. The first thing to try is restarting Xcode. If it still fails, Xcode generated a bad project (this can happen with any kind of project due to a bug in Xcode 4). If this happens, you'll need to start over and create a new project.
Linker Warnings
The first time you build your framework target (not the Universal_Framework target), XCode may complain about missing folders during the linking phase:
ld: warning: directory not found for option
'-L/Users/myself/Library/Developer/Xcode/DerivedData/MyFramework-ccahfoccjqiognaqraesrxdyqcne/Build/Products/Debug-iphoneos'
If this happens, simply clean and rebuild the target and the warnings should go away.
Core Data momd not found
Xcode builds managed object model files differently in a framework project than it does in an application project. Instead of creating a .momd directory containing VersionInfo.plist and the .mom file, it simply creates the .mom file in the root directory.
This means that when initializing your NSManagedObjectModel from a model in an embedded framework, you must specify your model URL with an extension of "mom" rather than "momd":
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MyModel" withExtension:@"mom"];
Unknown class MyClass in Interface Builder file.
Since static frameworks are statically linked, the linker strips out any code it thinks is not being used. Unfortunately, the linker does not check xib files, and so if a class is referenced only in a xib, and not in objective-c code, the linker will drop that class from the final executable. This is a linker issue, not a framework issue (it also happens when you build a static library).
Apple's built-in framworks don't suffer this problem because they are dynamically loaded at runtime and the complete, unstripped dynamic library exists in the iOS device's firmware.
There are two ways around this:
-
Have end users of your framework disable linker optimizations by adding "-ObjC" and "-all_load" to "Other Linker Flags" in their project.
-
Put a code reference to the class inside another class in your framework that always gets used. For example, suppose you have "MyTextField", which is getting stripped by the linker. Suppose you also have "MyViewController", which uses MyTextField in its xib file and doesn't get stripped. You could do the following:
In MyTextField:
+ (void) forceLinkerLoad_ {}
In MyViewController:
+ (void) initialize
{
[super initialize];
[MyTextField forceLinkerLoad_];
}
Option 2 is more work for you, but if done right it saves the end user from having to disable linker optimizations just to use your framework.
History
Mk 1
The first incarnation. It used a bunch of script hackery to cobble together a fake framework. It exploited the "bundle" target, setting its type to a relocatable object file.
Mk 2
This version took advantage of the template system to do most of the work that the script used to do. Everything (including the script) was embedded in the template.
Mk 3
This version does away with the "relocatable object" hackery and builds a true static framework, with all the abilities of an OS X static framework. This solves a number of linker, unit testing, and workspace inclusion issues that plagued the previous hacky implementations.
It also includes the concept of the embeddedframework, which allows you to include resources with your framework in a way that Xcode understands.
Josh Weinberg also added some tweaks to make it build in the proper build directory with scheme-controlled configuration, and behave better as a subproject dependency.
It now requires some small modifications to Xcode's specification files in order to support true static frameworks, and thus comes with an install and uninstall script.
Mk 4
This version gives you the choice of installing the "real framework" template or the "fake framework" template. Both come with an install script, but only the "real framework" installer needs to modify Xcode.
This also fixes some issues that the fake framework had in Mk 2 (such as the curious behavior of embedding the full path to the compiled files within the files themselves, resulting in warnings when building with that framework).
License
Copyright (c) 2011 Karl Stenerud
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in the documentation of any redistributions of the template files themselves (but not in projects built using the templates).
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
