We're hiring!

We're actively seeking developers and designers for our Detroit & Ann Arbor locations.

Building a Universal Framework for iOS

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, armv7, and i386 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.

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 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 statically1 linked against. This is particularly useful in iOS development because an application can be built for the simulator (i386) and the device (armv6, armv7).

1 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 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

  1. File -> New -> New Project (Command-Shift-N)
  2. Select Frameworks & Libraries under iOS
  3. Select “Cocoa Touch Static Library” and click “Next”
  4. 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.

  1. File -> New Target
  2. Select “Other” under iOS
  3. Select “Aggregate”
  4. 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.

  1. Select MyLibrary-iOS target
  2. Select “Build Phases”
  3. Click “Add Build Phase”
  4. Select “Add Run Script”
  5. 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:

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" &&

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}" &&

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 here.

Copy header files into place

  1. Select “Add Build Phase”
  2. Click “Add Copy Files”
  3. Set “Destination” to “Absolute Path”
  4. Set subpath to ${BUILD_DIR}/${CONFIGURATION}-iphoneuniversal/${PRODUCT_NAME}.framework/Versions/A/Headers/
  5. Add header files that should be public to the list

Configure aggregate target build against the ‘Release’ configuration

  1. Product -> Edit Scheme
  2. Choose aggregate target (MyLibrary-iOS)
  3. Select “Run”
  4. Choose “Release” under Build Configuration

Build and verify framework

  1. Select aggregate target
  2. 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/
â

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.

Justin DeWind (42 Posts)

This entry was posted in iOS / OS X and tagged , , . Bookmark the permalink. Both comments and trackbacks are currently closed.

49 Comments

  1. Posted January 10, 2012 at 8:43 am

    Great post. We have been wanting to do this type of thing for a while.

    • saltfactory
      Posted April 12, 2012 at 3:10 am

      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.

  2. Cory Wiles
    Posted January 23, 2012 at 3:16 pm

    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

  3. Justin DeWind
    Posted January 23, 2012 at 9:08 pm

    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.

  4. Cory Wiles
    Posted January 23, 2012 at 9:21 pm

    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

    • Justin DeWind
      Posted January 24, 2012 at 10:10 am

      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

    • Justin DeWind
      Posted January 24, 2012 at 2:58 pm

      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”.

  5. Cory Wiles
    Posted January 24, 2012 at 6:20 pm

    Here is the result of the xcodebuild: https://skitch.com/kwylez/g5m7b/build-bash-364×97

    “Build Active Architectures Only” is set to No

  6. Deepika
    Posted February 1, 2012 at 2:03 am

    i am also facing the same error after following all the steps from your tutorial. Is there any solution yet?

    • Justin DeWind
      Posted February 3, 2012 at 8:38 am

      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.

  7. Cory D. Wiles
    Posted February 5, 2012 at 9:26 am

    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.

    • Justin DeWind
      Posted February 5, 2012 at 2:04 pm

      Sure. My GitHub username is dewind.

      • Luke
        Posted February 7, 2012 at 11:22 pm

        seems BUILD_DIR is wrong

        • Posted February 21, 2012 at 3:37 pm

          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

  8. Cory D. Wiles
    Posted February 5, 2012 at 9:47 pm

    https://github.com/kwylez/StaticFramework-Sample-Project

    You should have push access. Let me know if you don’t.

    • Posted April 23, 2012 at 4:15 pm

      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!

  9. Luke
    Posted February 6, 2012 at 12:54 am

    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?

    • Cory D. Wiles
      Posted February 6, 2012 at 10:03 am
      • Posted February 21, 2012 at 4:04 am

        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!

  10. Hans Kloss
    Posted February 7, 2012 at 8:24 am

    It is not working for me either, same error. Looks like a bunch of commands in the build static library action are failing.

  11. Justin DeWind
    Posted February 7, 2012 at 9:15 pm

    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.

    • Luke
      Posted February 8, 2012 at 1:27 am

      Well done, Justin! Now the question is how to use this framework ?

      • Justin DeWind
        Posted February 9, 2012 at 8:21 am

        As in. How does one use a static framework in Xcode?

      • Posted February 21, 2012 at 1:15 pm

        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,

  12. Cory D. Wiles
    Posted February 8, 2012 at 9:21 am

    Is it better not to have XCode manage the build directory (derived)?

    • Justin DeWind
      Posted February 8, 2012 at 9:41 am

      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

      • Posted February 21, 2012 at 12:07 pm

        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.

  13. Cory D. Wiles
    Posted February 9, 2012 at 8:26 am

    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”.

    • Justin DeWind
      Posted February 9, 2012 at 8:40 am

      No problem,

      Glad you enjoyed it and are getting use out of it. :-)

  14. Cory D. Wiles
    Posted February 9, 2012 at 4:37 pm

    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.

    • Posted February 21, 2012 at 5:14 am

      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

  15. Prasad
    Posted February 14, 2012 at 7:21 am

    Great tutorial to create universal framework manually. Thanks. Much appreciated.

    • Justin DeWind
      Posted February 14, 2012 at 10:42 am

      Thanks!

  16. MandyW
    Posted February 22, 2012 at 5:42 pm

    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…

    • Justin DeWind
      Posted April 11, 2012 at 8:14 am

      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.

    • Posted September 6, 2012 at 2:53 am

      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.

  17. Alien
    Posted March 1, 2012 at 1:11 am

    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

    • Justin DeWind
      Posted April 11, 2012 at 8:17 am

      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.

  18. Ankur
    Posted March 6, 2012 at 4:56 am

    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.

  19. Posted June 29, 2012 at 1:57 pm

    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?

  20. Jan
    Posted July 18, 2012 at 4:02 am

    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

  21. Posted September 5, 2012 at 7:48 am

    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.

  22. Yin Zhemin
    Posted September 5, 2012 at 4:20 pm

    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

  23. James
    Posted September 28, 2012 at 8:59 pm

    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

  24. Nivas
    Posted October 26, 2012 at 5:26 am

    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 ??

  25. Posted November 18, 2012 at 8:42 pm

    Thanks for the great tutorial, easy to follow and well described

  26. Posted November 23, 2012 at 1:14 am

    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?..

  27. Devin
    Posted December 5, 2012 at 3:19 pm

    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!

    • Justin DeWind
      Posted December 5, 2012 at 3:24 pm

      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.

One Trackback

  1. [...] more background can be found in this and this great [...]