One of the projects I am working on at the moment could possibly be deployed to the iPhone, iPad and Mac OS X. There is quite a chunk of behaviour that I would like to extract into a common library, and then just develop the UI for the respective platforms in their own projects. The project structure I have in mind is:

BDCommon – A shared library that contains logic that is common across all applications

– A shared library that contains logic that is common across all applications MyMacApp – A Lion-based Mac application that depends on the BDCommon project

– A Lion-based Mac application that depends on the BDCommon project MyPhoneApp – A universal iOS application that depends on the BDCommon project

As you probably know, when it comes to linking libraries, iOS apps can only link static libraries and the preferred approach for Mac OS X apps is to use a framework. So, I decided to create a single project that has two targets:

BDCommon.framework (used when linking from a Mac OS X app)

(used when linking from a Mac OS X app) libBDCommon.a (used when linking from an iOS app)

This post describes the steps I performed in Xcode4 to achieve this.

Create Common Project

What you are about to do is create a new MAC OS X Framework project called BDCommon . This will be the containing shell for both targets.

Create new Mac OS X Framework

Create a new Mac OS X Framework:

Set the product name and company identifier:

And save the project:

Configure BDCommon Project

Now we need to configure our newly create BDCommon project to support having two targets (one for the framework and one for the static library). The default behaviour of Xcode4 is to use the target’s name as the product name. This is OK if you only have one target, but because we want to build two products that have the same “name” (ie. BDCommon.framework and libBDCommon.a ) we have to have two targets, both with different names. Hence, we need to decouple the product’s name from the target’s name.

In build settings for the BDCommon target, change the Packaging/Product Name from $(TARGET_NAME) to BDCommon :

We will now rename the name of the target itself to BDCommon.framework so that we can easily recognise it in the list of targets:

One last thing is to rename the Scheme to BDCommon.Framework (like renaming the Target, this is mostly a cosmetic act that helps us distinguish between the framework’s scheme and the static library’s scheme:

Perform a build. Confirm that the BDCommon.framework gets created:

Now would probably be a good time to commit your changes to git.

Adding the libBDCommon.a Static Library Target

So, we now have a project that contains a target that will build a framework for our Mac OS X apps. Next, we want to add an additional target to build a static library for our iOS apps.

Choose the File/New/New Target… menu option and add a Cocoa Touch Static Library:

Set the name to BDCommon and add it to the BDCommon project:

Again, because we want to be able to rename the target, we need to decouple the product’s name by hard-coding it to BDCommon :

Let’s rename the target to libBDCommon.a so that it is more obvious which target is which:

And lastly, let’s rename the scheme to libBDCommon.a to match the target name (again, to make it easier to distinguish between the two targets’ schemes):

Perform a build for both schemes ( BDCommon.framework and libBDCommon.a ). Here is where you will notice what I believe is a bug in Xcode4:

Despite Xcode4 reporting that the build was successful, libBDCommon.a is depicted in red (indicating that it hasn’t built successfully). If you choose Open in Finder for both products, you will notice that it can find where it built BDCommon.framework , but not where it built libBDCommon.a .

It turns that it has actually built libBDCommon.a – it just displays it in the wrong colour. Using Finder, you can navigate manually to the common build directory and see that they do indeed get built successfully:

Almost done now. If you have a look at the files in your Project Navigator, you will notice that the BDCommon group is duplicated. This is due to Xcode4 being “helpful” and generating code when you added the second target. Note that it hasn’t actually generated two copies of the physical files… it has just created several references to the existing files. If you expand both BDCommon groups, you can delete the one with the fewer entries:

Make sure, though, that you choose Remove References Only though, otherwise, it will actually physically delete the files.

When you deleted the references in the step above, you also would have removed the membership of those references for the physical files from the two targets. You need to make sure that both BDCommon.h is in the membership list of both framework and static library targets.

The convention for shared libraries (whether they be a static library or a framework) is to have a single monolithic header file (in our case, that will be BDCommon.h ) that imports all the other .h files. At the moment, we don’t have any other .h files to include, so go ahead and remove all content from BDCommon.h so that it is an empty file.

Make sure that BDCommon.h is declared as a public header for both targets:

You can safely remove BDCommon.m now, as it no longer serves any really purpose. Note that this time, you will really delete the file (not just the reference).

Perform another build for both schemes and make sure your products get rebuilt correctly.

Again, this would be a good time to check your code into git.

Adding Some Behaviour to the Common Project

Over time, I expect that my common project will evolve to contain many different classes. To illustrate the process for the purpose of this article, we’ll add a simple class called BDLog that has a single method that outputs Hello world . Clearly, this is a contrived example, but it should give an indication on how to add new functionality to your common library.

Add a new Objective-C class:

Set the class name to be BDLog :

And save to your BDCommon group. Make sure you included it in both the BDCommon.framework and the libBDCommon.a targets:

Add a static method definition called log to BDLog.h :

+ ( void ) log ;

And a corresponding implementation in BDLog.m :

+ ( void ) log { NSLog ( @ "Hello world!" ) ; }

Now you need to declare it as a public header for both your targets so that it is accessible from the application projects:

Modify BDCommon.h to add the following line:

#import <BDCommon/BDLog.h>

When building your libraries, Xcode4 copies the public headers for your static library into a directory like .../DerivedData-dflasdfiouknclskjdchvlawioiewhsk/Build/Products/Debug-iphonesimulator/usr/local/include . This is fine, but it means that you need to change the header search path for every project that wants to include it, which is a pain in the ass. What we can do to make this easier is to rely on a little bit of knowledge about the list of directories that Xcode automatically uses when building. As it turns out, it always adds a directory called include to the header search path. To take advantage of this, we will change the Packaging/Public Headers Folder Path from /usr/local/include to include/BDCommon (per the screenshot below, you can also use $(PRODUCT_NAME) which will automatically substitute BDCommon ). Using this path, allows us to import our headers with a statement like #import <BDCommon/BDCommon.h> . This step is only required for the libBDCommon.a target.

Perform a build and check that both the framework and static libraries still build correctly.

Creating a Mac OS X Application

Alright, we are now at the moment of truth. We need to see whether all the hard work above (which should be a once-off setup) allows us to share common code. To do this, we’re first going to create a Mac OS X application and link to our framework.

Create a new Mac OS X app:

Set the name to MyMacApp – although it doesn’t really matter… we are just verifying that the framework will link OK:

And save the application:

Open the Build Phases tab for your application, and expand the Link Binary with Libraries section:

Click on the + , and with a bit of luck, our newly created framework should appear in the list of frameworks in the workspace. Select it, and choose Add:

Open up your App Delegate class implementation and add the following statement at the top of the file:

#import <BDCommon/BDCommon.h>

Now add the following code to the applicationDidFinishLaunching: method (if all goes well, Xcode4 will even auto-complete the class and method names for you):

[ BDLog log ] ;

Build and run. If everything goes to plan, you will see Hello world! appear in the console output.

Creating an iPhone Application

One down, one to go. Same basic deal as before – we are going to create an iPhone application, but this time we will link in the static library, instead of the framework.

Create a new iPhone app:

Set the name to MyPhoneApp:

And save the application:

Open the Build Phases tab for your application, and expand the Link Binary with Libraries section:

Click on the + , and our newly created static library should appear in the list of libraries in the workspace. Select it, and choose Add:

Open up your App Delegate class implementation and add the following statement at the top of the file:

#import <BDCommon/BDCommon.h>

Now add the following code to the application:didFinishLaunchingWithOptions: method (if all goes well, Xcode4 will even auto-complete the class and method names for you):

[ BDLog log ] ;

Build and run. If everything goes to plan, you will see Hello world! appear in the console output.

Linking Static Libraries That Contain Categories

If you use Objective-C categories in your common library, you will likely get an error when you link your static library against the iOS app. Apple has written an article that describes the process to resolve this problem. In a nutshell, though, you can modify the Other Linker Flags in your iOS app to include the -ObjC and -all_load options:

Wrapping Up

There are a few things that I haven’t yet touched on that I may include in a future post:

Bundling your library for distribution to other developers

Private header files

As this was my first foray into this topic, I would be very interested in hearing feedback from you. In particular, if there are pieces of the above that are incorrect or misleading, please let me know and I will correct ASAP.

Drop me a line at craig (at) blackdogfoundry (dot) com