Apple has invested quite a bit of time into making it easy to compile for a number of different architectures in XCode. For instance, compiling a library into its “armv6”:http://en.wikipedia.org/wiki/ARM_architecture, “armv7”:http://en.wikipedia.org/wiki/ARM_architecture, and “i386”:http://en.wikipedia.org/wiki/Intel_80386 variants is just a matter of specifying the supported architecture types. However, there isn’t a built-in mechanism to take the binaries built for the various architectures and merge them into a universal iOS “framework”:http://developer.apple.com/library/mac/#documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WhatAreFrameworks.html#//apple_ref/doc/uid/20002303-BBCEIJFI.
Before we go through the steps of building a universal iOS framework we should first review _what_ a framework is and _why_ they are useful.
What is a “framework,” and why are they useful?
Apple defines a framework as:
… a hierarchical directory that encapsulates shared resources, such as a dynamic shared library, nib files, image files, localized strings, header files, and reference documentation in a single package.
So instead of having header files and binaries in disperate locations a “framework”:http://developer.apple.com/library/mac/#documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WhatAreFrameworks.html#//apple_ref/doc/uid/20002303-BBCEIJFI brings everything together into one package (a directory with a known structure). Packaging a library as a framework simplifies things for developers because it not only provides a binary to link against but it includes all of the necessary header files to reference as well.
What is a “universal” framework?
A universal framework can be defined as a framework that contains a binary which has been built for a number of architectures (armv6, armv7, i386) and can be _statically_[1] linked against. This is particularly useful in iOS development because an application can be built for the simulator (i386) and the device (armv6, armv7).
fn1. Statically linking a library resolves symbols at compile time and embeds the library into the application. It is not possible, currently, to create a dynamic framework for iOS.
Building a “universal” framework
Firstly, there is a project called “iOS Universal Framework”:https://github.com/kstenerud/iOS-Universal-Framework that simplifies the process of building a universal framework by providing an XCode template. However, I think it is still a meaningful exercise to understand _how_ a universal framework is built using XCode.
Lets get started:
Create a New Project
# File -> New -> New Project (Command-Shift-N)
# Select Frameworks & Libraries under iOS
# Select “Cocoa Touch Static Library” and click “Next”
# Provide a name for the library
Configure Architectures
By default the static library is configured to only build for armv7 so we need to add armv6 and i386 to ensure that the static library is built for older devices (iPhone 3G/Original, early generation iPod Touch) and the simulator.
Create Aggregate Target
An aggregate target aggregates other targets together. In effect, it wraps a number of script executables and copy file tasks in a specified order.
# File -> New Target
# Select “Other” under iOS
# Select “Aggregate”
# Give it a name (MyLibrary-iOS for example)
Build Static Libraries
Under the “Build Phases” section of the aggregate target we are going to add a build phase that compiles a static library for i386 and ARM. In the next step we’ll merge the binaries into a fat binary.
# Select MyLibrary-iOS target
# Select “Build Phases”
# Click “Add Build Phase”
# Select “Add Run Script”
# Name it “Build Static Libs”
xcodebuild -project ${PROJECT_NAME}.xcodeproj -sdk iphonesimulator -target ${PROJECT_NAME} -configuration ${CONFIGURATION} clean build CONFIGURATION_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphonesimulator
xcodebuild -project ${PROJECT_NAME}.xcodeproj -sdk iphoneos -target ${PROJECT_NAME} -configuration ${CONFIGURATION} clean build CONFIGURATION_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphoneos
Build Universal Binary
We are going to add another “Run Script” phase that builds the framework itself. The script is going to:
1. Create a directory structure that mimics the same directory structure seen in Apple’s dynamic frameworks.
SIMULATOR_LIBRARY_PATH="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/lib${PROJECT_NAME}.a" &&
DEVICE_LIBRARY_PATH="${BUILD_DIR}/${CONFIGURATION}-iphoneos/lib${PROJECT_NAME}.a" &&
UNIVERSAL_LIBRARY_DIR="${BUILD_DIR}/${CONFIGURATION}-iphoneuniversal" &&
UNIVERSAL_LIBRARY_PATH="${UNIVERSAL_LIBRARY_DIR}/${PRODUCT_NAME}" &&
FRAMEWORK="${UNIVERSAL_LIBRARY_DIR}/${PRODUCT_NAME}.framework" &&
# Create framework directory structure.
rm -rf "${FRAMEWORK}" &&
mkdir -p "${UNIVERSAL_LIBRARY_DIR}" &&
mkdir -p "${FRAMEWORK}/Versions/A/Headers" &&
mkdir -p "${FRAMEWORK}/Versions/A/Resources" &&
2. Merge the static libraries built for the various architectures into a fat binary using a tool called lipo.
# Generate universal binary for the device and simulator.
lipo "${SIMULATOR_LIBRARY_PATH}" "${DEVICE_LIBRARY_PATH}" -create -output "${UNIVERSAL_LIBRARY_PATH}" &&
3. Move the appropriate files into place.
# Move files to appropriate locations in framework paths.
cp "${UNIVERSAL_LIBRARY_PATH}" "${FRAMEWORK}/Versions/A" &&
ln -s "A" "${FRAMEWORK}/Versions/Current" &&
ln -s "Versions/Current/Headers" "${FRAMEWORK}/Headers" &&
ln -s "Versions/Current/Resources" "${FRAMEWORK}/Resources" &&
ln -s "Versions/Current/${PRODUCT_NAME}" "${FRAMEWORK}/${PRODUCT_NAME}"
The full script can be found “at github”:https://gist.github.com/1472295.
Copy Header Files into Place
# Select “Add Build Phase”
# Click “Add Copy Files”
# Set “Destination” to “Absolute Path”
# Set subpath to ${BUILD_DIR}/${CONFIGURATION}-iphoneuniversal/${PRODUCT_NAME}.framework/Versions/A/Headers/
# Add header files that should be public to the list
Configure aggregate target build against the ‘Release’ configuration
# Product -> Edit Scheme
# Choose aggregate target (MyLibrary-iOS)
# Select “Run”
# Choose “Release” under Build Configuration
Build and Verify Framework
# Select aggregate target
# Build it (Command-B)
Verify that the binary was built for the correct architectures using lipo:
lipo -info build/Release-iphoneuniversal/MyLibrary-iOS.framework/MyLibrary-iOS
Architectures in the fat file: build/Release-iphoneuniversal/MyLibrary-iOS.framework/MyLibrary-iOS are: i386 armv6 armv7
Ensure that the header files are copied into place (XCode is known to mess this up on occasion):
tree build/Release-iphoneuniversal/MyLibrary-iOS.framework/
build/Release-iphoneuniversal/MyLibrary-iOS.framework/
├── Headers -> Versions/Current/Headers
├── MyLibrary-iOS -> Versions/Current/MyLibrary-iOS
├── Resources -> Versions/Current/Resources
└── Versions
├── A
│ ├── Headers
│ │ └── MyLibrary.h
│ ├── MyLibrary-iOS
│ └── Resources
└── Current -> A
7 directories, 3 files
h2. Conclusion
Creating a universal framework certainly requires a fair amount of upfront work. However, it is a great mechanism to distribute your library to the masses without making _them_ work to use it. There is not any configuration (header paths) or tweaking (warnings, ARC vs non-ARC files) required on the part of the user. Which means less work for _you_ having to respond to issues and complaints.
Update 02/07/2012: I’ve updated the scripts to ensure that they work when XCode is configured to choose the build directory (derived). The “Build static libs” script is the one that must be updated.
Great post. We have been wanting to do this type of thing for a while.
Thanks for this post that is the greatest. I hope to share good post in korean to students. I promise you that include your blog link in my post.
Justin,
I am trying to turn my static lib into a universal framework via your instructions, but there are some inconstancies that I am seeing between your screen shots and my version of Xcode (Version 4.2.1,Build 4D502). I have also read there was a recent change to the archs variable from Apple and that might cause issues with compiling for simulator.
There error that I am getting is:
lipo: can’t open input file: /Users/cwiles/Library/Developer/Xcode/DerivedData/StaticFramework-akqynrxfwazyswaujkdqvysbbznj/Build/Products/Release-iphonesimulator/libStaticFramework.a (No such file or directory)
Command /bin/sh failed with exit code 1
Do I need to change the scheme for the actual static library target for Run to “Release” or just for the Aggregated Target?
Thanks,
Cory
Interesting. Would you mind telling if there is anything under Build/Products/Release-iphonesimulator? It may be related to the ARCHissue but I believe I addressed that in the post where I state: “By default the static library is configured to only build for armv7 so we need to add armv6 and i386”. If i386 is not specified that i386 (simulator) lib will not be built.
Thank you for the quick response. Below are screenshots of my aggregate build settings (ARCH) and out put directory. Based upon the directories I am seeing for each ARCH build…do I need to change the scheme for the static lib target as well for “Run” to be RELEASE as well?
https://skitch.com/kwylez/g582y/xcode – ARCH
https://skitch.com/kwylez/g582p/xcode – ERROR
https://skitch.com/kwylez/g5827/products-bash-233×71 – BUILD OUTPUT
Hm,
It doesn’t appear to be building the release lib. Which is odd, because the scripts I provided should build against whatever the current configuration is set to (Release/Debug). It shouldn’t require any manual intervention on your part.
Try this from the command line and let me know the results:
xcodebuild -project #{PROJECT_NAME}.xcodeproj -target #{TARGET} -configuration Release build
One more thing:
Can you tell me whether “Build Active Architectures Only” is set to “Yes”. If it is, that may be your problem. That should be set to “No”.
Here is the result of the xcodebuild: https://skitch.com/kwylez/g5m7b/build-bash-364×97
“Build Active Architectures Only” is set to No
i am also facing the same error after following all the steps from your tutorial. Is there any solution yet?
I think in order for me to properly diagnose this issue I would need access to the project. I created a new project using the same version of XCode and followed the above instructions and was unable to reproduce the same issue.
I have a test project that I will post to github today. What is your github username and I’ll give you push access. That way I can see the diffs as to what I would have done wrong.
Sure. My GitHub username is dewind.
seems BUILD_DIR is wrong
Yep,So maybe you need to inatsll them.Check on your folder:/Developer/Platforms/iPhoneOS.platform/Developer/ and see what SDKs you have.iOS 5 doesn’t use armv6 anymore
https://github.com/kwylez/StaticFramework-Sample-Project
You should have push access. Let me know if you don’t.
Great article. I did run into the Lipo error as well using your updated scripts even. Super weird. But I cloned your sample project and rebuilt it. Works like a charm thanks!
Got error: Command /bin/sh failed with exit code 1
Would you post the project to github then we can check what’s the problem?
https://github.com/kwylez/StaticFramework-Sample-Project
Almost two years after this post and it just hleepd me expliain why one of our libraries can’t decrement the version number without breaking pretty much everybody else who depends on that library.Thanks!
It is not working for me either, same error. Looks like a bunch of commands in the build static library action are failing.
I’ve updated the post to reflect the changes necessary to get the build work. Specifically, you’ll want to grab the “Build static libs” script.
Well done, Justin! Now the question is how to use this framework ?
As in. How does one use a static framework in Xcode?
hey buddy,The i386 is the smtilauor architecture and the .o files are always related to the debug symbols.So your problem is probably related to the debug symbols generation.Did u tried the sample project?Cheers,
Is it better not to have XCode manage the build directory (derived)?
Cory,
I haven’t been privy to the benefits of it. The only thing that it does is ensure that you don’t need a gitignore for the build directory. Maybe there is something I’m missing?
Additionally, if you use
xcodebuild
via the command line it uses the build directory specified by the target and does not use the derived directory regardless of the preferences set in Xcode.I prefer using the target specified build directory for the following reasons:
-Makes it consistent between xcodebuild and Xcode
-The directory is always known
-It is configurable
Well, when you set Generate Debug Symbols to NO this issue shluod be solved.Besides, you can change ${CONFIGURATION} key-word to Release . This will solve the problem securely.
Justin,
Thanks for that insight. I have created a new branch for the sample project called ‘configbased’ to utilize xcconfig’s (which I have never used until yesterday). I am hoping that I can set it up where a user can specify, as you mentioned above, where their build directory is for the target for xcodebuild or still use the derived data directory. The other option that I am looking at is trying to detect if user is using derived data directory and then in the build script copy the .framework back into the project somewhere that is easy to find. I might be over thinking it, but hopefully it will be helpful to others.
Once again…great article. This really helped me understand the build process a lot better. It also feels cleaner than hacking together a “bundle”.
No problem,
Glad you enjoyed it and are getting use out of it. :-)
Justin,
I was able to move most of the configuration over to xcconfig files for the project and target. You can now leave build directory as “derived” or specify a different directory at project or target level, as well as, change the target name. Need to do a little code cleanup and then i’ll post to github. I would love your feedback on it.
Thank you for that tip. Even thugoh my project is an application, turning off ZeroLink allowed the unit testing target to reference the application assembly. Previously, I had resorted to adding my source files to my unit testing target, which was obviously suboptimal.David H. Silberdhs at the domain DavidSilber.name
Great tutorial to create universal framework manually. Thanks. Much appreciated.
Thanks!
Thanks so much for a great blog post. We’ve successfully created a framework based on your article but we have had some problems including ui resources in the framework. I ended up creating a separate .bundle for these which works fine with nibs but doesn’t seem to work with storyboards. Just wondered if anyone had experience of adding storyboards to a framework…
Hi Mandy,
I haven’t integrated resources like that directly using a static framework. A static framework is compiled directly into the binary and I am not sure how resources would be treated and whether they would be copied over properly.
I followed all the steps you mentioned. It created successfully tree structure you have given me, only one thing missing that is all header files I have added (given under section “Copy header files into place”). Please tell where I am missing.
We are able to successfully create the framework, but resources [image files .png] are not coming in the view.My custom framework itself has UITableViewController and am using images for UITableViewCell. It was working when I use those framework files as part of the app itself.But when I move all those files [.h/.m & .png ] to a custom framework and link it with another app, images for TableView cell are not coming :(
Also am not able to debug the framework, breakpoints are not hitting inside the framework code.
Any help would be deeply appreciated. Thanks
It is quite possible that in static frameworks resources on not bundled into the .app file. With regard to debugging you may be able to accomplish this so long as XCode is aware of the original source code and the compiled symbols and code match up correctly. For instance, you could have a reference to a project that contains the source code you are trying to debug.
Thanks Justin for wonderful article. It really helped me. I have a query for you. How can I enable breakpoints in my library so that I can debug it.
[…] more background can be found in this and this great […]
Thanks for your great article!
I am able to successfully create and use my framework project, however have been unable to access the Core Data .xcdatamodeld compiled resource .momd from my framework.
Any suggestions how to include Core Data resources in your framework?
Hi,
thanks for the article but what is missing is a note on how to actually distribute and use the framework in other projects.
The other thing are the resources. You quote Apple saying that a Framework encapsulates libraries, headers and resources but don’t explain how include the resources. I think this would be appreciated by everybody if you do so ;)
cheers
Jan
I followed all the steps you mentioned. It created successfully tree structure you have given me, only one thing missing that is all header files I have added (given under section “Copy header files into place”). Please tell where I am missing.
Thanks for great post.
I want to make FacebookSDK framework and OAuth framework for iOS.
I downloaded source code from Github.com.
well, there are .sh files.
How can i use it?
Can you help me to make FacebookSDK framework?
Best Regards,
Yin Zhemin
Hello ,
Building the framework this way will prevent my source code from being leaked to my customers when I pass them the framework?
Regards ,
James
Hi,
Nice tutorial, I Have a question is it possible to import a static library into the aggregate and build.
Which means importing static library into this custom framework is possible ??
Thanks for the great tutorial, easy to follow and well described
Hi,
i have followed this method to create framework it’s working fine but after some changes i want to create new framework .a file not updating with new changes.
Can you please help me to make a framework?..
Hey Justin,
Great article here! Love how concise it is!
I did have a question. I was following this tutorial using XCode v4.5.
Looking inside /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/ I noticed I only have “iPhoneOS5.0.sdk”
As a result, when I build and run lipo -info on my /build directory, I noticed it only produces an armv7 and i386 compatible build only. Can you please elaborate on what I need to do to make it output armv6 compatibility as well?
Thanks!
Devin,
XCode 4.5 has removed support for armv6. Which means there aren’t any compilation tools available to build for that architecture. Specifying the “armv6” architecture has no effect anymore unfortunately.
If you can find the armv6 gcc and linking tools you may be able to build it and add it to your universal binary.
I created my own framework like here. I used it in a project and it works already. Now i want to upload the project to iTUnesConnect but i get some errors. I couldn’t find the solution. Here error codes ERROR- ITMS 90087, ERROR- ITMS 90209, ERROR- ITMS 90125
I would be appreciated if you could help me