— Jun 19, 2011
Apple Frameworks bundle the headers required to user a library along with the library itself, which makes them easy to use in an application. Integration of built-in frameworks is a two step process:
- Add the framework to the “Link with Binary Libraries” phase
- Import the framework in your source (
However, frameworks are not (officially * ) available on iOS, so we are left with traditional static libraries. Static libraries are a little more cumbersome to use because there is no standard or convention for including their public headers. Usually you must either:
- Drag the headers into you Xcode project, or
- Set the
USER_HEADER_SEARCH_PATHSin your project.
While neither task is particularly difficult, it would be better if the extra step of adding the headers wasn’t necessary. I wanted to make my library as easy to integrate framework, and the headers were the primary thing preventing this.
Xcode allows headers to be declared as “Public” as part of the “Copy Headers” build phase. If you’re using a Workspace, Xcode will “install” the public headers to the shared build location of the Workspace, instead of the system root. Unfortunately the path where the library’s headers and installed by default does not match the path where all Projects search by default, preventing them from being imported easily.
We can fix the public header path by setting the
PUBLIC_HEADERS_FOLDER_PATH variable in the library’s build settings:
PUBLIC_HEADERS_FOLDER_PATH = include/$(PROJECT_NAME)
Now headers from
MyLib can be imported in other Projects in the Workspace with the familiar convention:
No changes headers need to be added to the project and no search paths need to be altered. The headers are just “there”.
The previous step will allow you to build and run the application, but the Archive build action will fail. There are actually two problems:
- Xcode uses different header search paths for archiving that it does for regular builds. It won’t find the
PUBLIC_HEADERS_FOLDER_PATHwe set above.
- If Xcode could find the headers, it would treat the headers it found in the
PUBLIC_HEADERS_FOLDER_PATHas build artifacts, and will create an
xcarchiveinstead of the desired
We can fix both problems by setting the
INSTALL_PATH build variable:
INSTALL_PATH = /../BuildProductsPath/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/
This moves the headers to a location in the default search path for archiving (which again is different than regular build), and moves the headers out of the
BUILT_PRODUCTS_DIR, so Xcode will make an
Putting it All Together
I bundled the build settings described above into a file called
Ive shared two stand-alone libraries which use this configuration:
and a project which uses both libraries:
AMFoundation from the
AMSpringboard library, or the
AMSpringboardDemo app is as simple a system framework:
After I did my initial work I discovered a blog post by Jonah Williams which has agreat description of the overall problem, but provides a slightly different solution.
A big part of my inspiration for this was Python’svirtualenv (and to a lesser extent pip). An Xcode Workspace is analagous to isolated environment created by
virtualenv, expect that modules (Projects) within the shared space are not easily imported. If static libraries were “packaged” like frameworks, we get a lot closer to having nice, portable sandbox environments for iOS projects.
* Karl Stenerud has done some great work on creating “fake” static frameworks for iOS, but they require installing additional templates or patching Xcode itself. I didn’t want to require the users of my library to make changes to their system.blog comments powered by Disqus