Monday, November 17, 2008

Building static libraries with the iPhone SDK

I have a client that wants to use my iPhone Radio Player code for their own radio station, however they want to be able to submit the app to iTunes under their own developer account. The gotcha is they are not purchasing my source code - they want to pay a smaller fee and just have me supply them with a binary image which they can then submit to Apple.

I do not have access to my client's Apple developer account, so I needed a way to supply the client with an Xcode project that includes a skeleton of source code along with a binary library that includes all the custom radio player functionality I have developed. This will allow the client to build the iPhone app and submit it via their account without giving the client access to my source code. Normally, one would probably create a custom framework to wrap up the custom radio player code, but in the case of the iPhone, this is not an option. Apple forbids the use of external frameworks or dynamic library linking in an iPhone app. Instead, I needed to create a static library containing my radio player code.

Fortunately, creating a static library for the iPhone turned out to be a fairly simple, but not at all well documented, process. If you already have an Xcode project, you can easily turn any portion of the source code into a static library. Here's how.

1) In your Xcode project, locate the "Targets" section under the "Groups & Files" sidebar. Right click on "Targets" and choose to add a new target:



Then, in the following dialog box that appears, choose a Cocoa Touch "Static Library" target:



Name the library whatever you'd like. In this example, I named the library "TestStaticLib."

2) Now, assign source code files to your static library. You can simply drag existing files from your project list. Note: don't add the .h files.



3) Then, remove those same source code files from your app target:



4) Add the new static library to your target app via the "General" settings for the target:



5) Edit your target app's linking settings to add "-ObjC" to the "Other Linker Flags". This is only required if your static library defines Objective-C classes that your app is going to reference:



6) Build your new static library for all the SDK targets (such as simulator, iPhone Device, etc.).

7) Build your app. It should now be referencing the static library rather than compiling from the original source code.

If you need to then distribute the app to a client, you can simply remove the source code for the static library and the client will still be able to build the app without having access to the original source code.

This technique might also be useful if you have some shared code you'll use in multiple applications, but don't want to recompile the shared code each time you make a new app that references it. Simply copy the shared library binary images to a specific location in your development environment and then have all your apps link with that code.

14 comments:

Biappi said...

Just one deployment tip

I've found that creating an Universal Binary gluing togheter the device and the simulator buids "Just Works"

It's considerably better to have only one .a than one for each architecture

d235j said...

Not really; only the developer will be testing the app in the simulator, so this will just result in extra cruft for the end user.

Phil said...

I don't think there will be extra cruft. If you make a "fat" library (with device and simulator), the linker will only link in the relevant portion of the library. This is different from dynamic linking.

costan+aw said...

If you're using static libraries to share code between iPhone projects, you can use a tool I wrote that takes the content of one project, and merges it with another project.

The tool is on GitHub, with complete instructions for using and extending it - http://github.com/costan/zerg_xcode

With my tool, you would wrap the library in an Xcode project, with all the files tucked away nicely under one folder and one group. Then the consumers would download the project somewhere, then “zerg-xcode import” from the project directory. The targets and files would be inserted in the consumer’s project, and the files would be copied on disk as well.

Most importantly, this also works for updates, even if files are added / removed.

Victor Costan said...

Thank you so much for the information!

I started with your post, and tried to automate it as much as possible. This is what I came up with: iPhone Development and Code Sharing

I would be grateful for any feedback from you and your readers.

Felix said...

Awesome! You saved me quite a bit of time in trying to figure this out by myself. Thank you.

Clint said...

Great post. I realize that in this case the source code can't be shared, but if other folks come across this tutorial and are thinking about sharing static libraries, along with the Xcode project and source code, they might want to consider using a cross-project reference. This allows you to avoid pre-building each static library for multiple runtime environments (and is perhaps more flexible than the "fat" Universal Binary option). I actually wrote up a tutorial on this at http://www.clintharris.net/2009/iphone-app-shared-libraries in case it's helpful to anyone else.

Phong said...

Great, let's try this

Nic said...

This doesn't solve the problem of having a company wide library that you would like to update on the fly.

Isn't there a way I can just add the header files to my search path and add the lib to the linker phase?

I've tried doing this but the only way to get it to work is what you have listed here ='(

Phil said...

Nic:

Look into Xcode / Preferences / Source Trees. You can store your private library / sources in a separate repository and separate folder, and Xcode can find them using the Source Trees settings. It can be updated independently.

You can also look into svn:externals if you are using subversion, for instance.

sri lanka proposals said...

thank you! That looks like a great resource

Carl said...

Hi,

Thanks for your post, I am trying myself to have some code from my iphone app to use in other apps with similar functionality, so from what I understand, static libs are the way to go (due to frameworks restrictions for AppStore apps).

I have followed carefully the steps you describe, however I for some reason get stuck in step 6 (Build static lib for all SDK targets). When I build the static lib target (right-click target, select "Build lib") I get 99 build errors, basically of the type:

error: syntax error before 'UIButton'/'UIView'/etc
fatal error: method definition not in @implementation context

Then on the errors window, I click on the .m file line, and the errors seem to origin in some .h files in the .m file imports.

At first I thought it had to do with some missing frameworks in the lib target, so I added the same fw's I had in the app target (UIKit, Foundation, etc) but that did not work.

I assume there is some linking missing that should be included. May you have any clue on what could be missing? I also see there is a Copy Headers directory in the lib target. Should I add the headers there too?

I would appreciate any help. Please let me know in case you need further information.

Thank you advance!

Carl said...

Hi,

I finally got build the static lib target. A bit of background:

The static lib I am trying to build in general consists of some view controller classes (UIViewController extending classes) and delegate classes (NSObject classes) which I plan to use as base classes on other projects. I use the delegate classes to hold some functionality outside of the view controllers, and in some cases I want some UI elements to be updated from these delegates, so they hold references to UIButton, UIView, etc.

Originally the delegates contained only a (Foundation/Foundation.h) import, and that was sufficient for a successful build of the project's target. However, when I added them to the static lib target and tried to build, it sent the "error: syntax error before 'UIButton'" type of errors and the build failed, again, only on the delegates.

The solution was to add the (UIKit/UIKit.h) import to the delegates and the UI* types were recognized so the build finally completed.

I still wonder why this was not needed on the project build. (it might be something simple. I am just beginning with ObjC and XCode - Please let know in case you know the answer!)

Again, thanks for the post.

andyeb said...

For anyone else having problems using categories from statically linked libraries, you need to add -all_load to "Other Linker Flags", in addition to -ObjC.


All content copyright © 2009  Brian Stormont, unless otherwise noted.   All rights reserved.