Categories
iphone

How to make an iPhone static library – part 2

UPDATE: Apple changed everything (again) without telling developers (again) and broke everything (again).

Current versions of Xcode (the minimum that Apple allows you to use) will *not* work with the architecture-link part of this blog post.

Instead, see this StackOverflow question I asked (and answered) with an updated technique: http://stackoverflow.com/questions/3520977/build-fat-static-library-device-simulator-using-xcode-and-sdk-4

Intro

This is NOT a perfect way of doing static libs on iPhone; however, it does work – I’ve used it in iPhone apps that are on the App Store right now – and I’ve been sitting on this post for over a month, improving it, tweaking it. It’s not perfect, but I think it’s better to put it out onto Google than to keep it quiet any longer.

NB: I’m not a Mac developer; the iPhone is relatively new to me. I apologize in advance for any stupid mistakes I make here where I should have known better!

PS: If you don’t read this blog normally, you might be dismayed at the amount of whinging about Apple’s less-than-perfect code and documentation in this post. Sorry. Let me just say that I am *trying* to be even-handed – and that that requires not only praising Apple, but also calling out their bad acts where relevant. I and many of my friends and ex-colleagues have suffered hours and days of wasted time – ON THIS TOPIC ALONE – because of tiny things that Apple staff “forgot to mention” or “didn’t bother testing”. It’s understandable, but it’s also painful.

A recap of what we need

You might want to read How to make an iPhone static library – part 1 if you haven’t already – it goes into much more detail on what we need, and why.

Recapping the key points:

  • One (or more…) “libSOMETHING.a” files that we can include in other projects
  • Must work both in Simulator and on Device
  • Must be distributable with NO SOURCE CODE (even if we ourselves have the source code – this makes version control much simpler!)
  • Must be implemented ENTIRELY inside Xcode – no external tools or dependencies
  • Must work on iPhone OS 2.x and iPhone OS 3.x

The Trick

On OS 2.x, I didn’t need to do this, but with 3.x, Apple hard-coded some new bugs into Xcode, making Xcode even harder to use.

Here’s the bug:

In the Build Settings for a Target, Xcode for iPhone OS 3.x and above will IGNORE the architecture instructions you give it

This means that “Compile these architectures” (a list of names) and “Only build the selected architecture” (a checkbox) are both ignored. You MUST treat these settings as “FYI only”, and be careful not to set them manually – your settings will be silently overridden. I’ve tested this by editing the Xcode .xcodeproj files (XML format) manually, and finding that they are ignored no matter what.

Instead, you MUST make all your architecture-configuration inside the “Base SDK” setting, and then NEVER EVER use anything EXCEPT the “Base SDK” setting in the main Build drop-down (the one you can see at all times when in the main Xcode view, up at the top left, just underneath the red, yellow, and green buttons (close window/minimize window/maximize window).

Unfortunately, this makes that drop-down ENTIRELY USELESS. Crap.

(UPDATE: See this link for another entire class of problems caused by the SAME major bug in Xcode for iPhone 3.0)

But … there’s a workaround!

You can create MULTIPLE values for Base SDK, and use the master drop-down to choose between them.

How?

By (manually) creating new Target’s.

Ultimately, we create a unique Target for every set of Build Settings we need. In the simple version, all we have to do is create one Target for the Device, and a separate Target for the Simulator. However, ultimately, you want (MUST!) create a separate Target for each combination of:

  1. Device / Simulator
  2. Debug / Release
  3. Main app / Lite app

…because if you don’t, then it’s highly likely you’ll accidentally change that drop-down one day, your project will screw up completely, and you’ll have no idea what’s going on. I have helped lots of people who broke their projects by using the master drop-down in OS 3.x. So many that I’m sure Apple *never* bothered to test this drop-down with basic Unit Tests, sadly, or they would have noticed quickly that they’d broken the settings that populate it.

Creating your Targets

First, understand that there are two “sets” of targets in Xcode (and there is NO way of telling the difference between them – this is a classic Xcode design bug).

There’s the Targets you can see – in the Targets twisty – and then there’s the “root” or “default” Target, which you access by scrolling to the top of the “Groups & Files” pane, and clicking on the first line (should be the same name as your Xcode Project). ALL values in the default target are copied into the real Targets, and then any “local overrides” in the real Targets are applied.

Usually, you only have 3-10 settings in the real Targets – all the rest is inherited from the default target.

Create new Targets by right-clicking on the Targets twisty and selecting “Add… -> New Target…” and selecting “Static Library” from the GUI. Make sure to give the new static lib a unique name!

MAKE SURE you do not re-use the same product name / target name / executable name that the original project’s target had (again, just trust me: this exposes so many horrible bugs in Xcode that you simply DO NOT want to go there…). Standard practice is to make TWO new Targets, include “Device” in the name of one of them, and “Simulator” in the name of the other one, and delete the original one (but only after you’ve tested the new projects are fully working!)

NB: Xcode has hard-coded a “magic” relationship between the Target name and the Project name and the Application Binary name. Just don’t go there. I’ve tried; it’s a horrible hacky mess, and if you screw it up you have to manually edit Xcode’s behind-the-scenes text files to fix it – Xcode is hard-coded to prevent you from editing it yourself, so just avoid it all and keep all your Target names “unique”.

One Target for device, one Target for simulator

On iPhone OS 2.x, you could create a single file, e.g. “libMY-LIBRARY.a” (NB: other standards, not important here, dictate that the filename must begin “lib” and must end “.a”), which contained both the Device binary and the Simulator binary; this is no longer possible on OS 3.x.

Instead, you MUST create two separate binaries. If you look at the mainstream 3rd-party libraries (e.g. AdMob), you’ll see that they give you TWO .a files, one “something-something-device.a” and the other “something-something-simulator.a”. This is because they’re using separate Xcode Target’s for Simulator and Device.

In each of those Target’s, simply:

  1. Click on the Target name in the Groups & Files pane
  2. Press command-I
  3. Go to the “Build” tab
  4. Change the Base SDK (It’s right at the top of the list) to whatever is appropriate – Simulator 2.2.1, or Device 3.0, or whatever

Now you have some “unconfigured” new Targets, but you need to add all your source files to them, or they will do nothing.

This is a bit trickier, because you have to: Close your eyes, cross your fingers (because there’s no way you can know if this will work or not – this being Apple (useless at UI design), and this being Xcode (highly random on what it does for drag/drop)). However, *trust me*: it just so happens that Xcode will 100% of the time in this situation do what you actually wanted: duplicate all the links to the files and move the duplicates to the place you wanted them.

So…

  1. Select everything in the original Target’s sub-groups, one sub-group at a time, and drag them to the equivalent sub-group of each of the new targets.

Making life simpler: screw Apple’s (broken) Configurations

Configurations are a pain in the ass – now that OS 3.x has broken the Base SDK and Architecture settings, they don’t really work; it’s far, FAR easier just to delete them.

So …:

  1. select the root Target (first item in the Groups & Files pane)
  2. hit command-I
  3. Go to the “Configurations” tab
  4. select each of them and press “Delete” button at bottom left of the screen, until there’s only one left

Of course, now you’ve lost the ability to dynamically change between “normal” and “highly optimized” compiles.

Or .. have you?

No, of course you haven’t – you just need to create some more Targets!

Assuming you deleted the “Release” Configurations, create 2 new Targets: “Release Simulator” and “Release Device”. Set them as per the original targets (you can in fact create them FROM the original Targets – although Apple has removed the keyboard shortcut, you can select each existing Target, and from the Edit menu select “Duplicate”; this is a good idea, as it spares you from manually setting the Base SDK and possibly getting it wrong).

Unfortunately, you will need to manually re-create the “Release” custom settings.

Create a new, virgin, Xcode project, set it to Release mode, and then open the Build Settings. From the second drop-down at top-left, titled “Show:”, select “Settings Defined at This Level”, to see ONLY the things that are “unique” to Release builds – and copy the changed settings across to your manually-created “Release” Targets.

How do I use this to Build?

The drop down at top-left of Xcode’s main window should now:

  1. NOT have multiple things in the “Active Configurations” section – instead it should have only one, either “Debug” (or “Release” – which ever one you didn’t delete). NB: this DOES NOT MEAN ANYTHING
  2. SHOULD have multiple things in the “Active Target” section – one for each different thing you think you want to build
  3. WILL have lots of things in the “Active SDK” section

This is VERY IMPORTANT:

ALWAYS leave the “Active SDK” set to “Base SDK”; this is to workaround the bug(s) in the version of Xcode that came with iPhone OS 3.x; you may be tempted to change it BUT DO NOT! Instead, only ever change the Active Target

This way, all you have to do is glance at the Active Target, and you will always – ALWAYS – know exactly what Xcode is going to build right now.

Eventually, Apple will (hopefully) fix the new bugs they introduced for OS 3.x, and we can go back to using the drop-down to select Configurations, SDK’s, etc – but, actually, now that I’m used to using Targets, I see it’s much, much easier – and a lot less error-prone. Basically, Apple’s GUI is too poorly designed, and it’s just easier to have ONE thing to click on, rather than lots of them. So, I think I’ll stick to it from now on.

How do I use this in a separate project?

Still a lot of work to do, I’m afraid…

But first of all, you need to right-click EACH of the items in the Products twisty (there will be exactly one for each Target you have), and select “Reveal in Finder”.

Then, you need to drag/drop the revealed files – one by one – into the APP project.

NB: a lot of people have their Xcode setup such that these binaries will be located in DIFFERENT directories – that’s why you should use “Reveal in Finder” to make sure that you are *guaranteed* to be drag/dropping the correct file. I’ve confidently dragged the wrong file before (an old build from a previous setup), and wasted hours trying to figure out why none of my source changes seemed to show up in the app. Oh, how I laughed when I worked out the cause (actually, I nearly cried).

Libraries: the problem(s)

First, you need to copy all the Header files. Frameworks do this for you – but, remember: Apple has banned us from using Frameworks on iPhone :(.

Second, you need to implement some minor manual fixes to make Objective C work correctly. Objective C is very, very hacky, and with external libraries you need to set an extra linker flag to make it link the libs correctly.

Copying the header files

More bugs. Xcode’s “Add Files” dialog box has multiple bugs that have been around for multiple versions of Xcode. I’ve found only one way of reliably adding header files for external libs – and it’s not perfect :(.

  1. create a folder on your hard disk, inside the LIBRARY proejct, called “[library] headers” where [library] is the name of your library
  2. inside Xcode, manually delete all headers from your LIBRARY project – but do NOT click “also move to trash”
  3. manually move all header files (using Finder) into the “library headers” sub-folder
  4. inside Xcode, select “add existing files” and re-add all the headers
  5. …now go to your APP project…
  6. drag/drop the “library headers” folder from Finder into your APP project inside Xcode
  7. you MUST select “Recursively create groups for any added folders” – there are bugs in the other option that cause it to break your Xcode project
  8. you SHOULD NOT select “Copy items into destination group’s folder”; any changes to your header files inside LIBRARY project will automatically be picked up by APP project; however, if you delete the source for LIBRARY project (e.g. if you do a CVS checkin and then delete the working directory), then the APP project will refuse to build any more… it’s up to you.

What’s wrong with this?

Only one thing: when you create new Header files in LIBRARY, they do not automatically appear inside APP. You have to manually drag/drop them from finder into Xcode when it has APP project open.

However, at least Xcode does automatically create a Group named “[library] headers” and place all the files inside that group, which is nice…

Fixing the Objective C linker

If you don’t do this, then several things go wrong:

  1. Interface Builder appears to work, but any time your NIB files reference a LIBRARY class, they will cause your APP to crash with “impossible” line numbers and “impossible” error messages”
  2. Funky code in your library that uses custom Categories on Apple classes will fail to run at all

Apple (and sites you’ll find on Google) warn you about the second item above, but I only discovered the first item by accident.

UPDATE: To fix the first item:

UPDATE: …well, the approach for the second used to work for me, but now it dosn’t any more. Maybe a bug in the latest SDK update from apple, may be something else enitrely.

To fix the second item:

  1. open up your APP project
  2. go to the root Target
  3. in the Build Settings page type: “Other Linker Flags” into the search box. You should get one result
  4. double-click on the RHS, and click the “+” button on the dialog that pops up
  5. type “-ObjC” into the second dialog, and OK out of there

UPDATE: here’s a link to Apple’s official explanation of the need for the -ObjC flag

I have heard rumours that you might also need “-all_load” for some libraries – but I’ve not investigated that myself, and I’ve not (yet) encountered situations where it’s needed, sorry!

UPDATE: With iPhone OS 3.0 and above, you often DO need the -all_load flag – it’s due to bugs in Apple’s shipped SDK for 3.0. Oops.

UPDATE: Other problems with Interface Builder

If you do NOT manually copy all your header files across to the application project, IB will stop working.

Here’s an explanation of how to trivially fix this in just a few clicks

That’s a great feature of IB. That’s terrible GUI design from Apple, though: the only way you’d ever find out about it is if someone else told you first. I’ve not yet heard of this technique anywhere except on that one linked page above. YMMV…

Conclusion

Hopefully I haven’t missed anything out. If I have, I’ll re-write the post as necessary when people point out what’s missing.

10 replies on “How to make an iPhone static library – part 2”

This is one of the most ridiculous things I have ever read. You simply don’t know how to use Xcode properly. There is nothing inherently wrong with that, but the fact that you can’t/don’t know how to use the product correctly does not mean that it has any bugs. Also Objective-C isn’t hacky, and linker flags are there for specific cases. In the case of static libraries, object code is not linked into the executable unless it is referenced. In Objective-C, a lot of constructs like categories are bound at launch by the Objective-C runtime, which makes it very dynamic. However, to the compiler, this just looks like unreferenced object code, so the compiler will omit it. When the runtime attempts to execute code that is not actually linked, of course your executable aborts. The -ObjC flag just tells the linker to copy all the executable object code into the final project regardless of whether it is referenced at link time (so that categories and other such dynamically loaded constructs can be used).

Also Apple says to use dynamic linking for Mac OS X. There is no contradiction, iPhone OS != Mac OS X. You should continue to build and link dynamically on Mac OS X, unless there is a compelling reason not to (for instance, embedding the lua runtime into some server or application). What kind of programmer did you say you were again?

Thanks for the comment.

I’m sorry that you misread my post, I’ll try to make those bits clearer here.

I agree entirely that “there is no contradiction” – I never claimed there was. Rather, I pointed out that IN PLACE OF documentation, Apple currently has (or at least “had” when I was reading it) circular references; the Xcode docs for static libs are “missing”, and instead there’s one of their little notes saying “this is missing because you shouldn’t be using static libs any more; click this link to go to the dynamic libs docs” (or something to that effect).

I’ll skip over the Obj-C-is-a-hack thing – that distraction was my fault, a throwaway comment referring to the language itself, which isn’t really at issue here.

W.r.t. the linker, I didn’t make it clear enough, but my complaints about linking are really only about Xcode’s failure to support this in a neat fashion. A proprietary IDE designed around a specific language shouldn’t require you to manually add “custom” linker flags for something that is a basic, normal use-case.

After all that … sadly, you had nothing to say about the core problem: how do you ( how are you supposed to ) setup, build, and link static libs on iPhone?

I would greatly appreciate it if you would enlighten the rest of us on that problem too. Thanks!

@fluxon

Thanks for commenting. Please read Part 1 of the article (link at the top). This should explain all the ways in which the StormyProds blog post is – effectively – useless here.

Sorry.

Nice article.. I completely agree with you on the arrogant Apple documents why you shouldnt use static opposed to dynamic libraries. For those who are ignorant to see the difference, static libraries has its uses and dynamic libraries are sometimes more hassle than its worth. If you dont think so, try compiling and maintaining more complex environments and you will find yourself recursively compiling just to get that one daemon up and running. I am not saying i favor static compilations, but they do have their uses and anyone who doesnt think so will get their head stuck in the near future.

@Corin

None of our current projects are OS 4, and we’re still using Xcode 3.1 for backwards compaitilbity, so I don’t know if it’s fixed.

However, I think one aspect of this is definitely going to be easier. With iPad, Apple “un-deleted” the support for universal binaries (which IIRC I originally used for doing static libraries), so you should at least be able to make a single unified lib again (but this is IIRC).

Comments are closed.