Categories
iphone programming

Using shared libraries for iPhone with multiple projects

You would think this is easy, seeing as how it’s an essential, simple, part of any programming project. HA! You obviously haven’t used Apple’s shockingly bad IDE (Xcode) much, have you?

You can read the extremely widely-linked blog post – http://www.stormyprods.com/blogger/2008/11/using-static-libraries-with-iphone-sdk.html – which is incomplete, and doesn’t work properly if you want to do auto-updates when source changes (which, in most cases, you do). Unfortunately, it doesn’t give any pointers to making linked projects work. I don’t blame the author for stopping at that point, their solution “works”, but I wanted to get the full benefits of auto recompilation.

You can google to try and find some documentation from Apple. I can’t find any, although I can find lots of other people on forums asking for documentation on how to do it themselves.

Eventually I worked it out, after hours of trial and error, and reading as much of the Xcode docs as I could find that seemed to relate to what was going on.

  1. Make a new project to hold your shared library
  2. Put all the .m and .h files for your library in that project (drag/drop them to the groups and files list)
    • XCode “feature”: it will by default fail to copy the files, even though that means everything will break later on, when you delete the originals. You need to tick the “copy files if necessary” checkbox)
  3. Create a “Static Library” build target
  4. Drag the .m files *only* to the static library build target’s “sources” twisty
  5. DO NOT DELETE THE FILES FROM THE MAIN PROJECT (because you’ve just created references, not copies, although it doesn’t tell you that … so Xcode will silently delete them everywhere, and OS X still doesn’t support undelete; Sulka tells me that Undelete will be coming to OS X in the next version. Oh, how I laughed)
  6. DO delete the files from the main Build Target
    • (again, they’re not actually files there, just references, so “deleting” them merely gets rid of the reference, not the file)
    • is this another bug in XCode? It seems strange that you use the “delete” funciton to do something that isn’t really a delete
  7. Click the NEW build target (the static library) and right click and select “build”.
    • XCode “feature”: if you highlight the thing you want to build, and hit Cmd-B to build, it WILL NOT BUILD the selected target, it will instead build the main target and ignore the target you actually wanted built
    • The *only* way to make cmd-b build the thing you want is to change some of the options on the Target dropdown at top left of the window – but you’ll have to change it back and forth each time you want to build somethign different with cmd-b … there seems to be no context-sensitive Build command in Xcode (bug?)
  8. If you want to test that your static lib built correctly (hint: you probably do)
    1. Add the static library to the main target
      • XCode bug: you can add the static library to the “direct dependencies” section. This has no effect; don’t do it. The static library MUST be added to the “linked libraries” section, i.e. the bottom panel of the General tab of the Inspector for the Build Target
    2. Write some code that uses your static lib – I suggest putting it in the app delegate class that Xcode auto-generated when you created the new project – and try building the main target (you CAN use cmd-b for this … unless you switched targets above, in which case you’ll need to switch them back first). If you have added the static lib in “the wrong place” or created it “in the wrong way” then this will fail with missing class errors etc

    All that has achieved is to compile your source code. No, seriously – it is THAT HARD. Now you have to actually try using it from another project.

    This is where the StormyProds link takes the easy way out (which only partially works): it simply copies the .a file (generated by the compiler) to somewhere on your hard disk, and has you hard-link to that file from other XCode projects. This will work; but it will also get out of date rapidly, and cause all sorts of bugs if you forget to update it. It will never auto-compile when there are changes.

    What Apple wants you to do (and for once I agree with them :) – this makes sense for most developers who aren’t using an external build system) is to link the projects directly, so that changes in one cause the other to automatically recompile *if necessary*.

    NB: for all my disappointments with Xcode, this is one feature I think has been extremely well done – it handles “links” between discrete Xcode projects extremely well, and (although the menus and docs for it are a bit hard to find) it mostly Just Works.

    1. Link the two projects
      1. Following Apple’s instructions here: http://developer.apple.com/DOCUMENTATION/DeveloperTools/Conceptual/XcodeProjectManagement/040-Files_in_Projects/chapter_5_section_7.html#//apple_ref/doc/uid/TP40002666-CJBJHJCJ – i.e.:
      2. Open the project that will import the static library
      3. Go to the “Project” menu and select “Add to Project”
      4. Find the .xcodeproj file on your harddisk that representst the other project, and select it
      5. Check it worked:
        • At the top of Groups and Files panel, you should see a blue twisty for your project, and just inside it a blue twisty for your imported / linked project
        • Inside the second twisty MIGHT be a .app file (representing the main target of the imported project, which you won’t need – you MIGHT have created a project wihtout a main executable, or deleted the spec for it, but by default you’ll have this)
        • Inside the second twisty should be a .a file (representing the static library from the imported project, which you DO NEED)
    2. Make the secondary project import the compiled static library from the first project
      1. Drag the .a file from inside the second twisty, and drop it inside the “Link Binary with Libraries” section of the main Build Target of the main project
        • DO NOT drop it anywhere else in the Built Targets section – it will work, but will have no effect (you do that in the next step)
        • If you do it correctly, you should see the static library appear with the same icon as each of your included Frameworks (all iPhone projects have at least 2 Frameworks normally: UIKit.framework, Foundation.framework), and appear right next to them in the same twisty underneath Build Targets
        • ALSO open the inspector for your build target and add a second dependency in the main dependencies section to the static lib – NOT the app – from the imported project.
        • (if you don’t do this, you’ll see weird things like the build compiling for iPhone, but not for the Simulator, for no apparent reason)

    Finally … drag and drop the header files from the original project’s location in Finder into the new project’s source folders. Do NOT check the “copy files if necessary”. Xcode will do a reference / softlink, so that changes to the header files in the original project are picked up. NB: you will need to do a drag/drop each time you add a new header file to the original project.

    (You *might* be able to get away with putting all the header files in a sub-fodler of their own in the original project, and drag/dropping that FOLDER into the new project, but … I tried that, and it seemed to sometimes fail to notice the new header files, so I’m not sure I’d risk it).

    Some fun errors:

    Line Location Tool:0: “_OBJC_CLASS_$_ScoreUploader”, referenced from:

    Various causes of this useless error message:

    1. you added a class to the source project … but forgot to drag/drop that .m file into the “Compile Sources” part of the Target for the Static Library in the source project (or you did so, but you didn’t link your xcodeproj’s directly … but forgot to recompile the source project (linking the projects, as described above, triggers auto-rebuilds and you dont see this problem so often))
    2. you had all the source in one project originally … but haven’t deleted FROM YOUR HARD DISK the source files for the source inside the original, main project

    (this is IMHO another bug in Xcode: it silently leaves the files in place, by default, and yet silently (secretly) uses them when compiling – but ONLY when dealing with imported libraries, NOT when dealing with local compilation. Huh?)

    Line Location Tool:0: literal-pointer@__OBJC@__cls_refs@NSMutableList in libhiscorelib.a(XMLSuperParser.o)

    You haven’t added the static lib as a non-lib dependency

    Some things to be aware of

    The header files that you drag/dropped from one project to the other have NOT been copied to the new project and are therefore NOT real files – they are references.

    If you delete the original files (in the statically compiled lib project) then the references in all other projects will vanish; be careful!

    If you updarte the original files (in the statically compiled lib project) then the referneces in all ohter projects will automatically update (with no UI indication of this).

    Unlike most normal IDE’s, there is no obvious UI indication that the files are not inside the current project (you can check by doing cmd-i on any of the files and looking at the value of the “Full Path” field, but otherwise you won’t see a difference).

10 replies on “Using shared libraries for iPhone with multiple projects”

I’m glad to hear that I’m not alone in thinking that Apple’s development tools are hard to use. I was excited to try out Mac development when I got my MacBook for my current job, but after less than a day I realized that Apple didn’t make it nearly easy enough. I wondered if I was the problem or Apple’s developer group was the problem. In the recent past I’ve done C#, MAXScript, and C++ Xbox 360 dev work, so it’s not like my skill set is limited.

I feel your pain! Not a day goes by where I don’t curse XCode, usually its ridiculous code completion “features”. Your blog post is great though, as this is something that we’ve just started doing as well.

By the way, I’d like to stress the “secretly using files that aren’t in the project but still on your harddrive” error in XCode. I’ve had it jump to an old implementation (the ctrl+alt+up shortcut) after renaming a .m file to .mm.

You can also delete .h files at random (delete references but leave the file on the HD) and your project will compile and run without trouble.

I think your post would be a lot more readable and useful if you removed the anit-XCode diatribe. If you have a chip on your shoulder about XCode the thing to do would be post a specific blog post with your grievances, and not litter an otherwise helpful post with it.

@andy

Being explicit about the Xcode bugs and flaws is IMHO an essential part of explaining the process; people need to know that when it goes weird it (probably) isn’t that they’ve done something wrong, it’s Xcode being passive-aggressive towards them.

If Xcode’s flaws and Apple’s bad docs (which tell you to use dynamic libs instead … despite the fact that apple has *banned* us from using dybamic libs on iphone) were not such a core part ofthis particular process then I wouldn’t mention them.

Except maybe for comic effect / sharing the pain – this is a blog after all, not a peer-reviewed tech journal. I get to be flippant and emotional :).

Good information; however, I would argue that the decision whether to link against a built, binary version of the library (a la StormyProds), or to link projects (which you describe) is complicated and should take into account the overall development strategy of oneself or one’s organization.

If you’re a lone-wolf coder just sharing a library between a couple of your own apps, then linking projects is probably the most convenient way to go. On the other hand, if you have three developers, one working on App A, another on App B, and a third on shared library X, then you can easily get into a situation where you want App A linked to libX-1.0, App B linked to libX-2.0, and Developer X working on libX-3.0.

Linking dependent projects ensures that all of your apps use the latest code, but it also forces you to upgrade all of your apps every time you want to upgrade your library; releasing and linking against specific versions of your library separates the decision to upgrade your library from the decision to upgrade your apps–which allows you to be more strategic in how you allocate your development time.

@clint

yeah. the more persepctives the better – there’s so many pitfalls in this!

I had a quick look over your instructions and I’d be interested in how the shared output build dir worked for you. As far as I can see, Xcode has some awesome fundamental bugs that make that a bad idea – e.g. I believe (as in – it happened when I tried it, but I wasn’t sure why, and maybe I just made a config mistake) that this will cause e.g. a debug build to overwrite the libs for the release build in that dir.

No problem?

Well, yes, actually, the moment you need to export that file to e.g. check it in to SVN. Or to email it to a client. Just one accidental rebuild of ANY of your projects using that directory while they are in the “wrong” mode will vaporize your otherwise working binary.

Of course, you shouldn’t be exporting direct from an output directory, but I haven’t yet worked out how to get Xcode to behave like a normal IDE and just do custom build outputs in a sensible manenr (I’m sure it’s possible, I’ve heard other people have done it, I just haevn’t worked it out yet myself)

@adam

Regarding the shared build output dir, I haven’t noticed any problems since I started using it in about four months ago. I have a custom logging library, for example, that is shared amongst several iPhone and OS X apps. The OS X versions show up in “Release” and “Debug” directories, the simulator versions in “Release-iphonesimulator” and “Debug-iphonesimulator”, and finally the device versions in “Release-iphoneos” and “Debug-iphoneos.” In other words, none of the builds seem to be overwriting each other (unless I’m totally missing something).

Of course I’m not disputing the issues you’re saying occurred on your machine; I’ve only used the “shared build output dir” in my own environment. That said, the Apple documentation actually suggests doing this (see the last paragraph in the “Referencing Other Projects” section of “Xcode Project Mangagement Guide”–http://tinyurl.com/c6c9vo), so hopefully it’s just a config problem on your machine?

Cool – sounds like I somehow managed to mess it up then, because I had everything in one flat directory IIRC.

I’ll try again soon, and see if I can get it work this time. Thanks!

Comments are closed.