»
February 08, 2009
»

Creating a Static Library for iPhone

Apple iPhone Developer agreement forbids using Frameworks in iPhone applications. The main reasons Apple does not allow using them is performance considerations and that developer-private frameworks won’t be reused in memory between iPhone applications anyway. But this doesn’t prohibit 3rd party iPhone developers to create static libraries for reusable code and possibly even distribute them for other developers while not providing source-code.

In this blog post I’ll try to describe the process of:

  • creating a static library for iPhone OS platform in Xcode
  • setting static library project as “Direct Dependency” for dependent project’s Target
  • including static library public (and only public) headers in dependent project’s “Header Search Path”
  • linking against created static library in another iPhone targeted application

Nearly everything in the following applies to Mac OS X static libraries and projects too. I’ll indicated the differences.

Create a static library

Although Xcode doesn’t have iPhone OS Static Library template as it do for Mac OS X, we can use Static Library → Cocoa Static Library template and switch SDK for it or we can create iPhone application project based on iPhone OS → Window-Based Application template and then create new iPhone OS Static Library Target what Xcode has for iPhone projects.

I’ve chosen to use iPhone OS template because created for us iPhone app Target will come in handy anyway, for example as a demo project or for some real-world testing of static library.

So, let’s create a project what a bit later will be the home of our new static library. I’ll name it “LibDemo”.

Select File → New Project… → iPhone OS → Window-Based Application.

static/1.png

Next, let’s create a Static Library Target. Right click on Targets,
Add → New Target… → Cocoa Touch → Static Library

static/2.png

I’ll name the Target “libDemo” (with lower “l”).

Now we have two Targets:

static/3.png

and two Products:

static/4.png

  • LibDemo.app
  • liblibDemo.a

liblibDemo.a certainly doesn’t sound right but it’s easy to fix. Xcode names Products based on Target setting “Product Name”, so let’s go and change it.

Right click on libDemo Target, select “Get Info”, click on “Build” tab. Search for “Product Name” and be shure to use “All Configurations” configuration. Set Product name to “Demo”, Xcode adds standard static library lib prefix:

static/5.png

Fine, now we have following Products:

  • LibDemo.app
  • libDemo.a

Next we can create some dummy class what will be compiled in our static library and what we will use in dependent project.

Create a class for libDemo

I’ll create AMZeeba class with header file what will expose -greet method.

Select File → New File… → Cocoa Touch Classes → NSObject subclass

This class should be compiled in libDemo so select both LibDemo and libDemo Targets.

static/6.png

Static library headers

Fine, now we have a class with a header and in dependent project (depending on setup) we have the choice of using all static library headers or just subset of them what we declare as “public” here (like when creating Frameworks).

I’ve chosen to use only public headers so I can be sure I’m not using anything I consider private from static library. So, if I’m distributing static library in binary form with public headers only I can be confident I haven’t anything forgotten to add to them.

To declare header public, we need to set it’s Role to “public”. Select Targets → libDemo and set “public” as Role for AMZeeba.h:

static/7.png

..and build our library.

Built static library

Xcode creates the following structure in librarie’s build folder:

ampatspell:~/Cocoa/static/LibDemo/build/Debug-iphonesimulator$ tree
.
|-- libDemo.a
`-- usr
    `-- local
        `-- include
            `-- AMZeeba.h

And while we’re here, let’s look at library symbol table:

ampatspell:~/Cocoa/static/LibDemo/build/Debug-iphonesimulator$ nm libDemo.a

libDemo.a(AMZeeba.o):
00000042 t -[AMZeeba greet]
00000000 t -[AMZeeba init]
00000000 A .objc_class_name_AMZeeba
         U .objc_class_name_NSObject
         U ___CFConstantStringClassReference
         U _objc_msgSendSuper

As we can see, the public headers are copied to usr/local/include and libDemo.a actually has AMZeeba class (.objc_class_name_AMZeeba) and methods (symbols) defined.

Everything looks fine, we can continue with libDemo.a client.

libDemo Client Application

In general there are (at least) two ways how to include code from static library project in dependent application. The simplest is just dragging headers and implementation files to dependent project and adding them to desired Target. Other way is actually to link against static library and it’s preferred way from code organisation point of view.

I’ll start with linking and then, in next section, briefly describe “dragging way” with it’s benefits and drawbacks.

Before we start setting-up linking against our static library, we need to create “Client” iPhone project.

Select File → New Project… → iPhone OS → Application → Window-Based Application

I’ll name this project “Client”. Build & Run. We should see blank, white window background in iPhone Simulator or on actual device.

Linking against static library

To link against a static library while continuing to work with both dependent application and static library we need to:

  • Add a cross-project reference to static library in dependent project
  • Add project to dependent project Target
  • Add static library Product to “Link Binary With Libraries” Build Phase
  • Set static library project as direct dependency to dependent project’s Target
  • Add custom “Header Search Path” to point to static library build output folder

Quite a list of tasks to create this setup but fortunately it’s easy to do. We will start with first.

Cross-Project Reference

We need to start with a cross-project reference to static library project. This is Xcode specific feature what allows us to use Products and set build dependencies between projects.

Drag blue LibDemo project icon from “Groups & Files” and drop it inside Client project. Check “Client” in “Add To Targets” table, do not check “Copy items into destination group’s folder” in the sheet what will appear:

static/8.png

Press Add, the result should look like this:

static/9.png

As you can see, there’s LibDemo.xcodeproj reference what lists two Products — libDemo.a and LibDemo.app. We’re not interested in LibDemo.app what eventually will be just a demo of libDemo.a functionality (or just disappear). But libDemo.a is the product we will use by linking against it.

Link binary, Direct dependency

To link binary with a static library we need to add it to “Linked Libraries” list for binary Target.

Unfold LibDemo.xcodeproj and Targets → Client → Link Binary With Libraries. Drag libDemo.a to Link build phase.

static/10.png

Now we’re linking Client binary against our staric library but it doesn’t mean the library is automatically rebuilt if we make changes in it’s code. To inform Xcode we want to keep library up-to-date and link against newest version in this Target, we need to set library as direct dependency for Target.

To set libDemo library Target as dependency for Client Target, right click on Client Target, select Get Info then click on General tab. Click add button under Direct Dependencies list, select libDemo.

static/11.png

As you can see, this panel consists of two parts:

  • Direct Dependencies
  • Linked Libraries

Actually we could use Add button under Linked Libraries to link against libDemo.a as we could just drag dependent Target under Target we want it depend to. Just two ways of doing it, both yields the same result.

To make sure our direct dependency setting works, let’s clean both projects then build only Client. This should trigger building of libDemo.a too.

Select Build → Clean (⇧⌘K) and click “Also Clean Dependencies”

Select Build → Build (⌘B) and Build → Build Results (⇧⌘B)

We should see that two Targets are built successfully:

static/12.png

But before we can start actually using library, we need to tell Xcode where to find library headers.

Public Headers

First I want to show the problem. Let’s add #import "AMZeeba.h" in ClientAppDelegate.m, build and examine build results:

static/13.png

Of course, Client’s app project has no idea where to look for AMZeeba.h1.

External headers in Xcode are searched using “Header Search Path” (and “User Header Search Path”) build variables. We need to add libDemo.a public header location to it.

Open “Client” Target info panel (Select Targets → Client and press ⌘I), type “header search” in search field under “Build” tab.

static/14.png

Double-click on “Header Search Path” and add:

${PROJECT_DIR}/../libDemo/build/${BUILD_STYLE}-${PLATFORM_NAME}/usr/local/include

The variables are:

PROJECT_DIR Client project’s root directory
BUILD_STYLE Debug or Release
PLATFORM_NAME iphonesimulator or iphoneos

Note: For Mac OS X Static libraries the search path value is:

${PROJECT_DIR}/../libDemo/build/${BUILD_STYLE}/usr/local/include

Grand finale

Now we can build Client and it should find AMZeeba.h.

static/15.png

…and this is the result we where looking for.

Download LibDemo & Client (36Kb)

Dragging thing

Creating a static library is not the only way how to create reusable code “bundles” for iPhone and for Cocoa development in general. On Mac OS X the preferred way is creating Frameworks but this is not an option for iPhone but there’s third way — dragging and dropping group of files from one project into another and adding them to desired Targets. This way we’re cross-project referencing source code. This has some benefits and drawbacks.

The benefit is simpler setup — we don’t need to create a static library Target, no header search pathes to set.

The drawbacks to name a few are:

  • when Group is cross-project referenced it’s contents are not automatically updated. So if we create a new header or class, this addition needs to be added to dependent project manually
  • library content is a part of dependent project’s source tree — no control over cleaning and rebuilding library versus dependent project.

In conclusion I must mention that it’s possible to mix static library approach with code cross-project references. It may be beneficial to start with static library by using drag-and-drop referencing and when library grows or there’s need to distribute static library in binary form, switch to static linking.


1 Actually I hope I’m wrong. Header Search Path alteration seems a bit hackish for something so simple.

 
Internet Explorer 6
Are you serious?