A very interesting yet very undocumented functionality of Xcode is that you can have sub-projects in your project tree. You can add an xcodeproj to your project and link to this project’s output.
This is exceptionally useful if you are developing some functionality in a contained project and now want to access this polished functionality from another project. Like for example you want to add to your app the capability of accepting HTML code copied from Safari and use my DTWebArchive classes for that. You could either copy all classes to your project, build two libraries (one for Simulator and one for Device, or lipo these two together), or build a static universal framework.
Or there is an option number 4 which I want to tell you about in this post. This option does neither copy source code nor does it involve building something upfront.
Note that Xcode 4.2 might cause you a bit of trouble until this works right. Some people told me that they did what I am about to describe in Xcode 3 to get it working. One way or the other this here should work, and if it doesn’t work for you then “you’re holding it wrong”. 😉
Sub-Projects, An Overlooked Option
This is the first case where I myself am using sub-projects. My DTRichTextEditor framework is making heavy use of my two open source projects. This is how the project tree looks on my machine:
The file system structure and the group structure are more or less identical. I have a folder Externals that contains clones of these two GitHub projects. When you add an xcodeproj to your project then it will show up as shown in the picture, allowing you to browse the source code contained in this sub-project as well. Better yet, when you’re debugging you get to step through the sub-project’s code as if it where local.
There are several caveats, besides sometimes having to restart Xcode several times for the project trees to show up. Xcode seems to take objection to you having the sub-project also opened at the same time. If you did that you will get an error message that the consistency of the sub-project cannot be verified. So make sure you don’t have the sub project open at the same time. You can edit the code from within the larger project anyway.
Preparing the Sub-Project
For being able to use an xcodeproj like shown above you need a couple of things, let me walk you through these.
You need at least one target that builds an iOS static library. Go to your project root and add such a target. Then pick all the headers and implementation files that you want to be part of this library.
I like to name the target different from the product. I want the target to be called “Static Library” and then I go into the build settings and call the product DTWebArchive, resulting in a product libDTWebArchive.a showing up under Products.
You do not need to modify the header use which can be project, private or public. Changing it from the default project just copies these headers into the product output path which is of little use. You might also want to adjust the name of the Scheme, or auto generate schemes for your targets. These schemes are just a convenience for you personally, they are not even saved in the xcodeproj file.
Make sure that your library builds without warnings or errors. Note that if you didn’t select any source files then the libtool will fail, too, without any useful error message. That happened to me on several occasions.
Adding the Sub-Project
Once you are satisfied with the static library close this project and open the other one that should contain it. Depending on how you work you could either keep all your projects in the same location next to each other or have the larger project actually also contain the smaller one. It’s up to you. I recommend the second option because your file system structure might be different from somebody else building this project. If you use absolute paths, or even relative ones pointing outside of the project root folder then you are asking for trouble.
When adding the sub-project you don’t need to select it to be a member of any targets just yet. Best if you just drag the xcodeproj from Finder into the project.
Next you need to add the path to look for the sub-project’s headers in to your build settings. You cannot add the .h files to your main project, but you can tell the compiler where it can find them. Specify the path relative to your project’s root folder. I chose recursive because I have multiple sub-projects and this way I only need to specify this setting once.
With this setup you can use the same import directives that you are used to.
You tell the linker to link against the static library target in the sub project. You set this up on the Build Phases tab, under “Link Binary With Libraries”. You probably want it to be “Required” for static libraries because we cannot build dynamic libraries (dylib) as Apple is using in their frameworks.
You also want to add the static library targets as dependencies for your app. This way the build system knows that it needs to rebuild the app object code if code in the dependency changes.
If you were only programming in c then that would be all, but since we are coding in Objective-C (and possible use ARC) there are “Other Linker Flags” we need to add:
- -ObjC because otherwise the linker does not properly load your classes
- -all_load if you have categories in the library, because otherwise those are not loaded
- -fobjc-arc if you are linking against a library built with ARC from an app that does not yet use ARC
Actually the first two are pretty standard, so if you have ever done anything with static libraries before you already know about these.
You see, there is not very much too it and you can stop copying your reused source code all over the place. Better you put it in your own foundation framework (as I have begun with DTFoundation), document it and add the appropriate unit tests. This way any new methods or polishing benefits all your apps instead of just the current one.