Easy, Modular Code Sharing Across iPhone Apps: Static Libraries and Cross-Project References

by Clint on March 30, 2009

Finding an elegant way to reuse and share code (i.e., libraries) across separate iPhone applications can be a bit tricky at first, especially considering Apple’s restrictions on dynamic library linking and custom Frameworks. Most people agree that the best solution is to use static libraries. This tutorial builds on that solution, showing how your Xcode project can reference a second Xcode project–one which is used to build a static library. This allows you to automatically build that static library with the rest of your app, using your current build configuration (e.g., debug, release, etc.) and avoid pre-building several versions of the library separately (where each version was built for a specific environment/configuration).

Problem: What’s the best way to share code across iPhone projects?

If you want to reuse/share code across different iPhone applications, you only have two options (that I’m aware of):

  1. Copy all of the source code from the “shared” library into your own project
  2. Keep the shared library code in a separate Xcode project and use it to build static libraries (e.g., libSomeLibrary.a, also referred to as “archive files”) that can be referenced by your project and used via static linking.
[ad]

The first option, copying the files, should be avoided when possible since it’s inherently redundant and contrary to the goal of keeping “common code” modular and atomic. It’s a much better idea to put the code in a static library since, as mentioned in the introduction, dynamic linking to custom libraries/frameworks isn’t allowed by Apple when it comes to iPhone apps. If you’re not sure how to put your code in a static library, I’d suggest taking a look at this tutorial on the Stormy Productions blog.

So we’ve established that the second option is preferable, but there’s a catch: you’ll need to build and distribute multiple versions of the static library–one for each runtime environment and build configuration. For example, you would need to build both “release” and “debug” versions of the library for the Simulator, as well as other pairs for the iPhone or iPod device itself. How can we avoid manually pre-building and managing separate .a files?

Solution: Static libraries that are built on-demand via Xcode cross-project references

The trick to avoid pre-building static libraries for each environment is to use an Xcode “cross-project reference” so that those libraries are built dynamically (i.e., when you build your own app) using your app’s current build configuration. This allows you to both reuse shared source code and avoid the headache of managing multiple versions of the library. Here’s how it works at a high level:

  1. The shared code lives in its own Xcode project that, when built, results in one or more static libraries.
  2. You create an Xcode environment variable with a path to the directory that contains the static library’s *.xcodeproj file.
  3. All iPhone apps that need the static library will use the aforementioned environment variable to reference the library’s Xcode project, including any static library in that project and the related header files.
  4. Each time you build your project for a specific configuration/runtime environment, the shared project library will also be built for that config/environment–if it hasn’t already–and linked with your executable.

In addition to solving the main problem (reusing code and avoiding management of multiple library versions), there are a couple of nice benefits to this strategy. First, if you make changes to the shared library source code, those changes will immediately be included the next time you build your own project (via the cross-project reference). Second, you can modify the Xcode environment variable to point to different versions of any project. For example, you might have separate directories for “somelibrary-1.0″ and “somelibrary-2.0″; as you’ll see in the detailed solution instructions, it’s easy to modify the environment variable and switch your project to a different version of “somelibrary.”

Other Solutions

zerg-xcode

Victor Costan has developed a slick command-line tool called “zerg-xcode” which helps you copy the source code from one Xcode project (i.e., a static library project) into another Xcode project. In addition to physically copying the files, it inserts the targets from the “library” project into your “app” project. If the library project changes, you simply run zerg-xcode again with the approriate commands to sync the files and targets. Some people may find this tool very useful; my personal preference, however, is to avoid making any copies of the source code files and stick to Xcode’s built-in “cross-project reference” mechanism.

“Fat” Universal Binary

Another approach is to “bundle” two versions of a static library into a single file, referred to as a “fat” universal binary (see this post on the Latenitesoft blog for an example). More specifically, one version of the library would be for the i386 architecture (i.e., the Simulator) and the second for the ARM architecture (i.e., the phone). This may be a perfectly fine solution for you if you really only need two versions, or if the source code for the library is kept private. That said, you’re still left with the task of maintaining pre-built versions of the libraries (plus the extra work of bundling them into the single file). In addition, I’m not sure that you can bundle more than two versions of the library into the binary (e.g., iPhone “release” and Simulator “release”, but not iPhone “debug” and Simulator “debug”).

How to Implement the Cross-Project Reference Solution

The instructions for setting up cross-project references to shared static libraries can be split into two parts:

  • Part 1: Global Xcode Settings
  • Part 2: Project-Specific Settings

Also, I’ll be using an example in the instructions to help illustrate things. A suitable example would be an application that needs to use a shared static library from a separate project. In this case, I’ll use a sample iPhone app called “Game Skeleton” (by Matt Sephton) that depends on a static library called libcocos2d.a (which is part of an open source project called Cocos2d-iphone).

Note: If it wasn’t already clear, cross-project referencing is a standard Xcode feature and is actually suggested by Apple in the official “Xcode Project Management Guide” documentation. You can certainly get some great bits of info from Apple’s guide, but as you’d expect, it’s a high-level document (hence my thinking that this tutorial could be helpful for others).

Part 1: Global Xcode Settings

The first step in getting your Xcode project to use cross-project referencing is to configure a couple of things that aren’t specific to any one project (i.e., global settings).

Set up a shared build output directory that will be shared by all Xcode projects.

Screenshot showing how the Xcode preference dialog and how to configure Xcode to use a global build output directory.

Screenshot showing how the Xcode preference dialog and how to configure Xcode to use a global build output directory.

  1. With Xcode open, select “Xcode > Preferences” from the menubar.
  2. Select the “Building” tab.
  3. Set “Place Build Projects in” to “Customized location” and specify the path to the common build directory you created.
  4. Set “Place Intermediate Build Files in” to “With build products.”

Why is this necessary?

A brief explanation of why this is necessary might be helpful for some people. When you build your app (i.e., Xcode project) Xcode generates one or more “products” (object files, libraries, etc.) in the project’s own build output directory, by default; it will then “look” inside this directory when it comes time to link everything together and make an executable, for example.

Once you start using cross-project references, you’ll essentially be building more than one project. However, Xcode will still only look in the immediate project’s build output directory for libraries. Apple therefore recommends using a shared build output directory for cross-project references (see the last paragraph in the “Referencing Other Projects” section of “Xcode Project Management Guide: Files in Projects”). This ensures that Xcode will always be able to find products from other projects builds.

Will a shared build output directory cause problems?

I’ve had some questions from folks about whether or not using a shared build output directory causes problems. While I’m certainly not an authority on building with Xcode, I can say that in four months of using this technique (with several projects and a few different shared libraries) I’ve not had any problems (such as a “debug” build resulting in a “release” version of your library being overwritten, etc.).

Apple’s Xcode documentations clearly states that “Within the build directory, Xcode maintains separate subdirectories for each build configuration defined by the project” (see the “Build Locations“ section of “Xcode Project Management Guide: Building Products”). For example, I have a custom logging library that is used by multiple iPhone and OS X apps. The OS X versions of the *.a file show up in “Release” and “Debug” sub-directories within the common build output folder, the simulator versions in “Release-iphonesimulator” and “Debug-iphonesimulator”, and finally the device versions in “Release-iphoneos” and “Debug-iphoneos.” In other words, none of the builds seem to be overwriting each other.

Add a “Source Tree” variable that Xcode can use to dynamically find the static library project.

“Source Tree settings” are basically Xcode environment variables that hold paths to directories on the file system; this allows us to make the cross-project references flexible and avoid hard-coded paths.

Screenshot showing the "Source Tree" settings tab within Xcode preferences.

Screenshot showing the "Source Tree" settings tab within Xcode preferences.

  1. Again, open the Xcode preferences.
  2. Select the “Source Trees” tab.
  3. Create a new Source Tree variable by clicking on the “+” button and filling in the columns. The screenshot above shows that we’re using “COCOS2D_SRC” for the cocos2d-iphone variable name and that it points to “/Users/clint/dev/cocos2d-iphone.googlecode.com/release-0.5.3″.Tip: avoid using special characters in the actual file path (i.e., stick to alphanumeric characters, underscores, and hyphens); this path will be used as a “Search Path” and Xcode seems to have problems with search paths that use characters like an ampersand (&).

Part 2: Project-Specific Settings

Once you’ve got Xcode configured to use a global build output directory and have a “Source Tree” variable pointing at your shared project, you’re ready to set up the cross-project reference, dependencies, etc.

Set Up the Cross-Project Reference, Header File Search Paths, and Static Library Linking

  1. Open your project in Xcode.
  2. In the “Groups & Files” pane of Xcode, select your project root and hit Option+Cmd+A (add to project).
  3. Find the Xcode project package for the project that contains the shared library. Using our example, we’ll select the Cocos2d-iphone Xcode project (cocos2d-port.xcodeproj):

    Screenshot showing a second Xcode project file being selected so that we can add a reference to it.

    Screenshot showing a second Xcode project file being selected so that we can add a reference to it.

  4. When the “Add to Project” dialog is displayed, use the same settings displayed in the screenshot below and click the “Add” button.
    Important: do NOT check the “Copy items” box.

    Screenshot showing which "add to project" options are selected when adding a cross-project reference in Xcode.

    Screenshot showing which "add to project" options are selected when adding a cross-project reference in Xcode.

  5. After you click the “Add” button the project will appear as a “sub-project.” In our Cocos2d-iphone example, it looks like this:
    Screenshot showing how the Cocos2d-iphone Xcode project appears as a "sub project" once it has been added to the main "Skeleton" project.

    Screenshot showing how the Cocos2d-iphone Xcode project appears as a "sub project" once it has been added to the main "Skeleton" project.

    Remember that you have not imported a physical copy of the second project–it’s a reference.

  6. When the cross-project reference appears select it and hit Cmd+i. Then change “Path Type” to be relative to the environment variable you set up in Part 1. In the example below, we’re using the COCOS2D_SRC variable:

    Screenshot showing the Xcode "Project Info" dialog for a project added via cross-project reference.

    Screenshot showing the Xcode "Project Info" dialog for a project added via cross-project reference.

Configure the Library Dependencies, Linking, and Header Files

  1. In the “Groups & Files” pane of Xcode, under “Targets”, select your main app target and hit Cmd+i. Then select general tab and add the static library(ies) your app needs from the shared project by clicking the “+” button under “Direct Dependencies”. In our example, we’ve added the “Chipmunk” and “cocos2d” libraries which are both built from the Cocos2d-iphone project:

    Screenshot showing an Xcode executable target being configured to depend on static libraries that are built from a cross-project reference.

    Screenshot showing an Xcode executable target being configured to depend on static libraries that are built from a cross-project reference.

  2. Click on the build tab and scroll down to the “search paths” section
  3. Important: If a hard-coded path to your shared project appears in the “Library Search Paths” field, delete it. This can be done by double-clicking the field and using the “-” button.
  4. Double-click on blank area next to “User header search paths”. Then click on the “+” button, check the recursive checkbox, and type in the Xcode environment variable that points to your shared project directory, surrounded by $(). The example screenshot below shows $(COCOS2D_SRC) being used:

    Screenshot showing how to configure the Xcode "User Header Search Paths" for a library that is being included via cross-project reference.

    Screenshot showing how to configure the Xcode "User Header Search Paths" for a library that is being included via cross-project reference.

  5. When you click OK and go back to the Build tab, the “user header search paths” text field should show an absolute path to your shared project directory. In our example, $(COCOS2D_SRC) expanded to the actual path and ends with “**” to show that the search will be recursive:

    Screenshot showing how an Xcode environment variable will be dynamically expanded to the actual, absolute path once entered using the $() notation.

    Screenshot showing how an Xcode environment variable will be dynamically expanded to the actual, absolute path once entered using the $() notation.

  6. Finally, click and drag the static libraries from underneath the cross-project reference to “Targets > {your target} > Link Binary with Libraries.” This ensures that that the .a files will be passed to the linker when you do the build. Here’s a sample screenshot from our example app:

    Screenshot showing how to link your executable to the static libraries via cross-project reference in Xcode.

    Screenshot showing how to link your executable to the static libraries via cross-project reference in Xcode.

Summary

To recap, if you need to share code across different iPhone projects I suggest 1) putting the shared code in its own “static library” Xcode project and 2) using a cross-project reference so that you can build the library with your own app as needed. The verboseness of this tutorial might give you the impression that setting this up is a lot of work; it’s not, really, especially if you do it more than once (which is likely, considering that the goal here is to share code across multiple projects). I’ve been using this approach for about four months with several projects and it’s definitely saved me a lot of time. Finally, there might be a better strategy that I’m not aware of; feedback and suggestions for alternate solutions are certainly welcome.

Clint Harris is an independent software consultant living in Brooklyn, New York. He can be contacted directly at ten.sirrahtnilc@tnilc.
  • Pingback: Easy, Modular Code Sharing Across iPhone Apps: Static Libraries and Cross-Project References — ClintHarris.net « Carbon Five News Network

  • Terry Grossman

    Clint,
    Thanks for posting such a clear tutorial! Here are some notes that others may find useful:

    1. A previous comment by Bob (bob 11.05.10 at 4:40 am ) seemed to indicate there were issues with classes in a static library correctly receiving notifications. As far as I can tell from my own tests, this is no longer the case.

    2. I am using the iOS wireless distribution model to get LITE and PRO versions of my app to beta testers, and my typical workflow is to build both apps (in device/debug configuration) then upload them to a fileserver for others to get to. I found that modifying the name of the configurations in each project (to Debug LITE and Debug PRO, respectively) resulted in separate directories for the builds being placed into the shared build directory (instead of overwriting each other in a common DEBUG directory). So far there haven’t been any bad repercussions to doing this.

    3. I won’t bore you with the reasons why, but I needed some classes from the static library to get linked into my app, even when there was no explicit reference to the class. My first approach was to have the app delegate call a dummy method in the class which worked, but was a horrible hack. A better, more elegant solution I found is to define a blank category on the class. This forces the class to be included in the project, provides an easy mechanism to do so, and doesn’t involve introducing a dependency in the app delegate. sweet! :)

    Terry

  • Pingback: Mobile App Developers List « UK iPhone App Developers

  • Pingback: iPhone Application Development Resources « ProductiseIT

  • TheMisfit

    I’ve been using this technique for a while now and it works just great :-)
    However now that I switched to Xcode 4 I got confused with the new interface and settings.
    Any change on an update for the latest version of Xcode?

    Pretty please

  • http://web.mac.com/dbolli Derek Bolli

    @TheMisfit

    In Xcode 4 the steps are fairly similar

    Attach the linked Xcode Project file using Cmd-Opt-A to select

    Click on your Project (not the linked Project) in Project Navigator

    Click a Target->Build Phases->Target Dependencies
    Add libs as Target Dependencies

    Click a Target->Build Phases->Link Binary With Libraries
    Add libs as Linked Binaries

    My question is how to have a linked Xcode project in the new Xcode 4 templates?

    Regards,
    Derek.

  • TheMisfit

    Derek,

    Thanks, somehowe my target depency got deleted.
    It’s working now as before.

    Jan

  • http://www.learn-cocos2d.com Steffen Itterheim

    @Derek:
    I’ve been trying to figure this out myself. The Apple templates don’t even have a subfolder of files that is copied, much less a referenced project. I was able to figure out how to copy an entire folder without specifying each individual file (in Definitions use Path key and just use the folder name). But including a .xcodeproj file with corresponding files of that project? No luck. Most of the time Xcode gets confused and creates folder references which should actually be files.

    If I can figure it out, I’ll post it on my blog http://www.learn-cocos2d.com

  • http://www.learn-cocos2d.com Steffen Itterheim

    @Derek:
    To clarify the problem: all files that are specified in the TemplateInfo.plist file (Definitions) are added to the newly created project. You *can* specify an .xcodeproj file and that works, however the files referenced in this .xcodeproj will not be copied automatically, so the referenced project is missing all its files. If you do specify these files in the Definitions part, they get copied but they are added to the new project, which is not what you want and sometimes faulty (files regarded as folders).

    The Xcode 4 template format is missing one crucial process: copy some files/folders but don’t add them to the new project. If anyone finds out how to do that I’d appreciate a response (comment here is ok, I get notified).

  • a

    I also had tonnes of problems with the finer details regarding files containing only categories.

    1. -ObjC is case-sensitive. That correction will at least permit a compile.

    2. Add the following in case of further “unrecognized selector” exceptions. iPhone in particular,
    -all_load

    As outlined in the following doc.
    http://developer.apple.com/library/mac/#qa/qa1490/_index.html

    Otherwise, thanks for the tutorial.

  • http://creativestride.com/boston-web-design Web Design Boston

    Greetings! Thank you for your thoughtful post!

  • Pingback: Labs: XCode Static Library Pitfalls | Kihon Games

  • Pingback: In Xcode 4, how create a multi-shared project setup? - Programmers Goodies

  • Anonymous

    Hi friends,
    After including the library, when i try to call a method of that library , xcode gives me a waring ‘No such method found’ . But, the code gets compiled and even the method is called. Whats wrong ? need ur suggestions.

  • http://www.facebook.com/jane.ahlquist Jane Ahlquist

    none