In my previous article about Sub-Projects in Xcode I showed you how you can have an Xcode project as a dependency. The advantage of this is that you can update your library from within the same project and debug into the sub-project’s code.
One thing that was not immediately obvious was how you deal with the problem that Xcode cannot find your sub-project’s library headers. Hence this writeup.
A header file generally informs the compiler what c-functions, global variables, Object-C methods and classes exist in an object file. All these are generally called “symbols”.
Build Process Review
The build process consists of these three steps:
- Preprocessor – this takes care of including/importing header files into your source code, essentially pasting the content of the specified file right into your source code for building. This step also replaces all instances of #defined values with what you defined them to be.
- Compiler – this goes through all the .m files and builds an object file with .o extension for each. Each item that is not coming from the same .m file is marked as an external symbol.
- Linker – this merges all the object files into one big binary while at the same time linking the symbols with the actual implementations.
Header files do not actually contain executable code, but instead they tell the compiler which names and functions it should accept. It is the linker then that resolves these references. In short: If you want to use something which is implemented outside the current .m file than you need the appropriate import for that.
There are two kinds of imports:
- <path/header.h> with angle brackets – these are meant for system or “global” includes.
- “path/header.h” with double quotes – these are meant for items that are limited in scope to your current project
You have probably used both already, the one with the angle brackets for including headers that Apple provided. The one with the double quotes for accessing your own classes. Paths are usually relative paths because everybody has a different folder structure on his hard disk.
Get the Headers into Your App
The easiest way to get the headers into your project is to just add them. But you should generally refrain from duplicating your code for convenience, because then you have to perform an update on every copy of the file. A variation of this theme would be to not add the file itself, but just a reference. But this approach would also make your project dependent on your folder structure. Don’t do it, just don’t.
If you have followed the Sub-Projects guide you are already perfectly set up to get to the header while still keeping the project self-contained and independent from the file system layout. You only need to tell the outer Xcode project – probably your app that includes sub-projects for some static libraries – where it can find the necessary headers from the sub-project. Again you have multiple options: you can point it to the source code folder where the .h files are located. Or you can point it to the build output folder. Both are equally viable but if you work with static universal frameworks – as I do – then you can only use option two without losing your mind.
On my projects that contain reusable code I usually have multiple targets. One for a universal static framework – which is very convenient to add to a project because it automatically sets up the library and header paths for you when you drag it into your project. I have a second target for a static library because this allows for using the project as a sub-project and have the outer project link to the static library product. Because the framework conveniently bundles together multiple .h files you usually have the general name of the framework as path, i.e. you find <MobFox/MobFox.h> as the import of choice. For for example an Apple framework <Foundation/Foundation.h>.
Contrasting the MobFox/ path the actual location of the header file is PROJECT_ROOT/Core, so no MobFox path component there. This is the reason why we cannot point the containing app to the source folder because there is no header file MobFox.h inside a folder named MobFox. So via approach number 1 we would never find the header, we want to take door number two which is to point Xcode to the static library build output folder. But for this to work some setup is necessary.
Specifying Public Header Output Location
After building the static library Xcode will also copy certain headers to the output folder. A header can be public, project or private. Public means that it will be distributed together with the library. Project and Private won’t. You can set this for every header file individually. Headers for objects that you want to be able to see from your app should be Public.
If you now build the static library and look at the build log you will see that at the very end the public headers are copied somewhere:
We are astonished to find a path ending in Build/Products/Debug-iphoneos/usr/local/include for the two public header files. The reason for this being the default setting is that traditionally people would build binary files for the system they are working on. But that’s not what we want. So we go into the build settings for the static library target and adjust this. Don’t put this entry into either the Debug or Release lines, but into the line above these so that it may override both.
When we now build we see the output go to Build/Products/Debug-iphoneos/MobFox – as we want it. It is this extra MobFox path component that enables us to use the <MobFox/MobFox.h> style of including later on.
Now back to our app that is including this sub-project’s static library. There we now need to tell the compiler where it can find the above folder. It so happens that when building Xcode usually uses the same output folder for all products it needs to build, namely whatever is in the $(CONFIGURATION_BUILD_DIR) variable at the time. This in turn is set by default to $(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME).
The final piece of the puzzle to know is where to put that. This depends on the include style that I explained above. Double quoted includes need the path in “User Header Search Paths”. Angle-bracketed includes need the regular “Header Search Paths”. We are forced to use the latter due to the framework. We don’t need to check the Recursive box because at the given location there is a MobFox folder.
The <Multiple values> might cause you to pause for a second, but remember what I told you above. The configuration (Debug/Release) is part of this path and so even though you put the same variable in there, the actual result is a different one. Note that these values are bold because the target settings override the settings specified by the project. You can revert to the project settings by selecting the line and hitting CMD+Backspace.
There is an alternate option to put this path in the “User Header Search Paths” instead, but then you need to set “Always Search User Paths” to Yes. This will cause the compiler to also search the user header paths for angle bracket includes. Though personally I prefer the other option that needs less settings.
The other steps for building are explained in the Sub-Projects posts. If you carry them out (link with target), you will find that now your app finds the headers and successfully builds.
If Xcode complains that it cannot find a header then you should make sure that it is indeed in a place where it can find it. There is an abundance of default settings that may seem daunting, but if you know what to look for you can quickly find why your header cannot be found.
I wrote this article because I myself was stumped by not being able to explain why a project couldn’t find the MobFox header. So another learning of this episode is that you should not assume that the owner of your sub-project has set everything up correctly. In my case I too was missing the custom header output path setting. Errare humanum est.