Static Libraries in Xcode

I wanted to do something that I think is straightforward, basic IDE functionality. Have a project that references a static library, use the static library from a client application (including referencing headers and linking with it), and have the library automatically rebuild when changes are made.

With most IDEs this is functionality that you get without even having to think too hard about it. You create a library, you say "this application depends on this library", and *poof* it just works. Xcode makes you jump through a few hoops.

Here are the steps involved in making this work, starting from scratch.

1. Create a new workspace. We'll start from scratch so we're both on the same page. BTW, I'm using Xcode 4.3.2.

2. Create the application project. I'm creating an empty iOS project called TestApp. Build and run, make sure it works.

3. Create the library project. I created a Cocoa Touch Static Library called TestLib. One gotcha here is that the default location for the library project will be inside the TestApp project. That's not what I wanted; I wanted the library and the project that uses it as peers in the Project Navigator (so that the library can be shared with other projects). To make this happen, make sure you select the workspace as the group for the new project when the file dialog appears to ask you where to save it:

201204270643-1
The first thing I want to accomplish is getting access to the library's public headers from the app. Xcode will copy the library's public headers as a build step, but this needs a little configuration. Here are the steps for that:

4. Mark the library project as Shared. Open the Manage Schemes… panel and click the Shared checkbox for the library project.
201204270648

201204270647-1

5. Add the headers we want to share to the static library's Copy Headers build step. Click on TestLib in the project navigator, click on the target, and drag TestLib.h into the Public section of the Copy Headers item.

201204270650

Now if you build the test library project, you should see this header being copied in the build output.

201204270707

That crazy target path means the headers are going into Xcode's derived data folder, in the folder it creates for products of our build. If you wanted your headers in a subdirectory so that you could include them as <TestLib/MyHeader.h> then you can edit the "Public Headers Folder Path" in Build Settings for the library project.

6. Set the Skip Install setting in the static library to YES. Otherwise, you can run into trouble archiving your project.

So the library is ready. Now we need to make TestApp depend on the library. There are a couple of steps to setting this up.

7. Edit the TestApp scheme, and in the Build section, add TestLib as a target to build.

201204270655

8. Add the TestLib library to TestApp. Click on the TestApp project, and in the TestApp target, on the Summary tab, in the Linked Frameworks and Libraries section, hit the + button and add the TestLib library.

201204270727

9. Add the common location for the public header files to the header search path.

201204270703

Now to test things out. Let's add some code to the object whose header file we made public. I'll add a simple method, "testString", that returns a string.

In TestLib.h:

@interface TestLib : NSObject
+ (
NSString *)testString;
@end

and

In TestLib.m:

+ (NSString *)testString {
return @"Hello";
}

And in the test application, where Xcode created a default AppDelegate object, simply add an NSLog that uses this test method to see the string printed.

In MYAppDelegate.m:

#import "TestLib.h"
// and later
NSLog(
@"%@", [TestLib testString]);

Build and run the application. With any luck, it will print "Hello" to the console.

Now, back in the static library, change "Hello" to "Goodbye", and run again. This prints "Hello", indicating that Xcode didn't recompile the static library. You want the static library to rebuild when you change files in it and run the test app, and there seems to be a bug in Xcode 4.3.2 that causes this to not work (so this fix may not apply if you're running a newer version). There's a workaround for this here but in a nutshell…

10. Select the static library in the Navigator pane.

201204270737-1

11. In the File Inspector on the right, change the Location to Relative to Build Products.

201204270739

12. Select TestApp and select Show in Finder. This should open a Finder window containing the Xcode project.

13. Select the Xcode project and right-click or Control-click to bring up the context menu, and select Show Package Contents.

14. Select the "project.pbxproj" file and Open it with TextEdit.

15. Look for the reference to libTestLib.a and change it so that instead of being a relative path, it's just the filename. For example, if it's "../Debug/libTestLib.a" change it to just "libTestLib.a".

16. Run TestApp again. This time, and from now on, the static library should rebuild whenever you change something in it.

Most of this writeup is basic "how to use a static library in Xcode", but steps 12 through 15 are a workaround for what seems to be a bug in Xcode.

If you still have trouble with this, check out this page, which has some great information on troubleshooting static libraries. This page is a modified version of the method he suggests, where I'm copying the public headers during the build process (which seems to be how Xcode is designed to work) and he's adding them as Source Trees. Switching between these two methods isn't a big deal and doesn't affect the overall process much, so you can pick one you like.