How to make framework for iOS
Previously I explained how to make a static library that simultaneously supports multiple different CPUs. At that time I was ignorant to the reason why there are no means to make iPhone frameworks. But I did not give up searching, especially since apparently a few individuals seemed to have pulled it off without properly documenting their method for others to duplicate.
The only guide I found is from two years ago, but I was able to verify that it works and so I will document it in an article so that many more people can use this great mechanism. But first, let’s briefly explore what it really IS that Apple does not want us to do. The generic statement that you can read on thousands of pages is “Apple does not want you to make iPhone frameworks”.
But is that really so? It turns out, this statement is not entirely accurate or rather specific enough to be the truth.
A framework is basically a folder with a certain sub-structure and that in itself cannot be something that Apple would “ban”. Then there is a property list describing some META information about the framework, still nothing illegal. Then there are headers and resources in specific location inside the framework folder structure. Nothing bad there. Ah, there is one thing that we REALLY cannot do: we cannot produce dynamically linked libraries.
And this is the real answer, so the above statement should be “Apple does not want you to make dynamically linked libraries”.
Now why could that be? Again the answer does not stem from the nefariousness of the mothership, but is actually rather simple. Dynamically linked libraries, are what caused “DLL-Hell” on early Windows platforms. The general idea is to put code that can be shared from multiple applications into one central place. Add a bit of versioning so that each program can state what version of the DLL it requires to work. Was a good idea, but in practice you accumulate so many different DLLs in different locations throughout a Windows file system that hell breaks loose.
Apple, being forever focussed on keeping things simple and stable, decided that there was to be no code-sharing between applications except for their own blessed system libraries. You remember, each app runs in its own sandbox. No code-sharing equals no necessity to have dynamic linking (for us developers). So Apple made the simple decision to omit the framework template from the iPhone project templates as this was the only way you could create dynamically linked libraries for Mac.
But this shortcut move not only eliminated an unusable feature (“code sharing between multiple apps via dynamically linked libraries”), but this is not the only reason why developers love frameworks or would also love to have them for iPhone. Frameworks neatly bundle headers and resources into a single package. This way you don’t have to distribute multiple files, but just one. Also, Xcode automatically takes care of the setup of paths leading to headers and resources. So using a framework in your code is a simple drag&drop operation.
Puzzling Together A Framework
So we want the compactness of a framework but we don’t need or want dynamic linking. But if we want that, we have to piece it together ourselves. I am building on the demo I did in the previous article where I end up with a merged static library. To summarize: it creates a static lib for iPhone and Simulator and merges it into a universal static library.
The first thing we need is an info.plist for the framework with some metadata. We leave everything as it is, our framework will forever be version 1.0, we only change the executable name and bundle identifier.
CFBundleDevelopmentRegion English CFBundleExecutable ${PROJECT_NAME} CFBundleIdentifier com.yourcompany.${PROJECT_NAME} CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType FMWK CFBundleSignature ???? CFBundleVersion 1.0 |
I chose to put placeholder in the two places we need to change later because in our building script this will be adjusted to fit our project name.
Next we need to replace all the extra build steps we put into the library demo with a single one to take care of the merging and construction of the framework.
The contents of this step is based on the work of Pete Goodliffe, but I adapted it to use the environment variables of which there are plenty set up by Xcode so that you probably need not make any changes to it as long as the framework has the same name as the project.
# name and build location FRAMEWORK_NAME=${PROJECT_NAME} FRAMEWORK_BUILD_PATH="${PROJECT_DIR}/build/Framework" # these never change FRAMEWORK_VERSION=A FRAMEWORK_CURRENT_VERSION=1 FRAMEWORK_COMPATIBILITY_VERSION=1 # Clean any existing framework that might be there if [ -d "$FRAMEWORK_BUILD_PATH" ] then echo "Framework: Cleaning framework..." rm -rf "$FRAMEWORK_BUILD_PATH" fi # Build the canonical Framework bundle directory structure echo "Framework: Setting up directories..." FRAMEWORK_DIR=$FRAMEWORK_BUILD_PATH/$FRAMEWORK_NAME.framework mkdir -p $FRAMEWORK_DIR mkdir -p $FRAMEWORK_DIR/Versions mkdir -p $FRAMEWORK_DIR/Versions/$FRAMEWORK_VERSION mkdir -p $FRAMEWORK_DIR/Versions/$FRAMEWORK_VERSION/Resources mkdir -p $FRAMEWORK_DIR/Versions/$FRAMEWORK_VERSION/Headers echo "Framework: Creating symlinks..." ln -s $FRAMEWORK_VERSION $FRAMEWORK_DIR/Versions/Current ln -s Versions/Current/Headers $FRAMEWORK_DIR/Headers ln -s Versions/Current/Resources $FRAMEWORK_DIR/Resources ln -s Versions/Current/$FRAMEWORK_NAME $FRAMEWORK_DIR/$FRAMEWORK_NAME # combine lib files for various platforms into one echo "Framework: Creating library..." lipo -create "${PROJECT_DIR}/build/${BUILD_STYLE}-iphoneos/lib${PROJECT_NAME}.a" "${PROJECT_DIR}/build/${BUILD_STYLE}-iphonesimulator/lib${PROJECT_NAME}.a" -o "$FRAMEWORK_DIR/Versions/Current/$FRAMEWORK_NAME" echo "Framework: Copying assets into current version..." cp ${SRCROOT}/*.h $FRAMEWORK_DIR/Headers/ #replace placeholder in plist with project name cat "${SRCROOT}/Framework.plist" | sed 's/${PROJECT_NAME}/'"${PROJECT_NAME}"'/' > $FRAMEWORK_DIR/Resources/Info.plist |
For lack of any better idea I used the unix command sed to replace the place holder in the framework plist with the real project name.
This technique renames the “executable” (which is really a .a static library) to be the same name as the framework, similar to how Apple does it. It would also work to name it correctly libSomething.a as my tests have shown. The linker does not care about the name of the library and apparently detects the type correctly.
Normally you would individually mark header files as either being private, project or public. And only public header files would get copied into the framework. I’m simply copying all header files to the framework header folder. A more elegant solution might be to do the copying via a copy phase and then individually select the headers you want to include. Or to have a dedicated Include folder under your project root holding the public headers.
If you have multiple public headers in your project you should make one umbrella header named the same as the framework containing imports for all the individual files.
Once more with Feeling … uhm, Framework
Having made sure that we chose “Use Base SDK” in the override list box we build it and find that our framework as appeared under build/Framework. From there we can use it in other projects. So let’s try this out in the same demo project we made previously.
We drag the framework into our frameworks group and find that Xcode automatically added it to the “Link Binary With Libraries” phase.
We add the same code as last time, setting the same “Other Linker Flags”, but this time we import the header differently:
#import <DTUtilities/DTUtilities.h>
|
That’s one of the advantages of frameworks that the setup of their search path is done such that you use angle brackets. This is usually reserved for system headers, but now we can do that as well. If we had made a DTUtilities.h header it would have looked even cooler.
And when he build it we see that our category extension for NSURL still works.
Conclusion and Caveats
This is a great method of bundling your libraries in a way that feels familiar to most iPhone developers. There are some caveats, maybe you or somebody smarter than us can figure these out:
- Resources don’t get automatically copied from the framework into your app bundle. You have to manually add references to these to the project. If anybody knows of a way to automate this as well, please let me know.
- As an alternative to manually copying resources you could make your code such that you simply don’t use any. For example: draw gradients in code instead of getting them from PNG files.
- Maybe somebody could make an Xcode template to take on the role of the script phase and provide the appropriate folder structure in the project for Resources, Documentation, etc.
- I’ve had a bit of success hacking together a project and modifying some files contained in the SDK. But I fear that this is a dead end because these modifications are not portable and would get overwritten every time you install a new SDK/Xcode. So the presented method is probably the best at this time.
- You need to make sure that you always set the override to “Use Base SDK”, otherwise you will build both static libraries for the same platform instead of getting a universal one
I find this whole thing really fascinating and once you understood the parts to the whole process it does not feel that difficult any more. If you discover any solution for the above mentioned problems please let me know. I feel that the ease of use of static frameworks by far outweighs the mentioned drawbacks.