4
Sep
2008
 

Cocoa Tutorial: Creating your very own framework

by Marcus Zarra

There are numerous situations where creating your own framework is advantageous. Perhaps you have a block of code that you use repeatedly in many different projects. Perhaps you are building a plug-in system for your application and want the infrastructure to be available both to the application and to any plugins that are coded.

A Cocoa framework is another project type in Xcode. The end result is a bundle, similar to an application bundle. Inside of this bundle is the compiled code you wrote and any headers that you want exposed. The headers are important. Without them, just like any other piece of Objective-C code, it is very hard to code against.

Building a Framework in Xcode

Building a framework within Xcode is straight-forward. There is already a project template specifically for Cocoa frameworks so that is where we will start.

New Framework Project

Once the framework is build, the rest of the code development is almost identical to an application bundle. C, C++ and Objective-C files can be included along with nibs, xibs, images and just about anything else we would stick into an application bundle. When the code is complied, the end result is also a bundle, again very similar to an application. The bundle structure, however, is a bit different and will be discussed in detail below.

Care must be given to the headers that we write and that we expose as part of that framework. In general, we really only want to expose the headers that users of the framework are going to interact with. If a header is completely internal to the framework then do not plan on exposing it. In addition, it is a good idea to have a wrapper header that users can import and gain references to all of the public headers. Since a framework will undoubtably contain more than one class file, how do we roll all of that up into one header? One solution I have seen is to create a separate header file and copy all of the interfaces into it. Unfortunately that solution can become unwieldy very quickly. My preferred solution is to build a “wrapper” header file that imports all of the headers that you want exposed. This is demonstrated in Code Sample 1.


Code Sample 1: Example.h

#import "ExampleOne.h"
#import "ExampleTwo.h"

Once the framework is built but before we distribute it, there are a few tweaks we need to deal with. First, we need to declare which headers are public and which are project. There is also an option for private but honestly I cannot see the logic behind them. If we flag a header as private it gets included in the resulting framework in a directory called “PrivateHeaders”. However, if we flag a header as “project” then it is correctly not included in the framework. Public headers are naturally included in the framework under the Headers directory.

Which headers should be included

For some headers, the answer to that question is easy. However, there are some gotchas that need to be planned around. If an object is referenced in one of the public headers then its header must also be public. Therefore if we want an object to be private/hidden/internal then we need to make sure there are no public references to it anywhere in any of the exposed headers. For example, if we had a an internal object called InternalObject and we referenced it in our header, the header for InternalObject would need to be public. See See Image 2.

All Public Headers

However, if the reference is an id instead, we can keep the InternalObject private and its header un-exposed. See Image 3.

Project Level InternalObject

On thing to note is that in both cases, the AbstractExample is public even though we do not want anyone extending it. Because both ExampleOne and ExampleTwo are subclasses of AbstractExample its header must therefore be included in the framework. To make this a little confusing, this is NOT the case if the application and the framework both exist in the same project! If we look at the included project (called Example), and turn AbstractExample to project, everything will work just fine. However, if we take that compiled framework and use it in the second included project (called Test), it will fail. Therefore depending on how we are going to use the framework, superclasses will probably need to be included.

Using the framework

Now that we have our framework built, how do we use it? There are three places we can put this framework. We can make it system accessible, user accessible or application accessible.

System Accessible

System accessible frameworks are just like the frameworks that come with OS X. They can be imported into any application. For this magic to work, however, they need to be installed in a system level folder; specifically /Library/Frameworks. Go ahed and look in that directory now. If your system is anything like mine it is empty. Why? Because system level frameworks require root access to be installed and therefore require privilege escalation to get them there. Generally this is a BAD IDEA and there are few frameworks that follow this path.

User Accessible

User accessible frameworks are very similar to System accessible frameworks. However they are stored in a user writable location instead; ~/Library/Frameworks. Go ahead and look for this directory also. Chances are the directory itself does not even exist. This is safer than a system accessible framework and if we have a framework that is going to be used by multiple applications then this might be a logical place for it. But both user accessible frameworks and system frameworks have the same technical challenge discussed below.

Application Accessible

Outside of Apple this is the most common usage of a framework. It is bundled with the application and only the application can access it. Both of the other locations for frameworks have one issue; how do you get them there? The answer? You include them with the application of course! However, if you have to include them with the application (and you will need to include them with EVERY application that uses them) then what exactly is the point of putting them somewhere else? This is one of the main reasons that System and User accessible frameworks really don’t have a point; again outside of Apple.

Setting the installation directory

Since it only really makes sense to have our framework be application accessible, we need to tell it that when we build it. While this is not important for an application that has the framework included in its build cycle, if we want our application to be used by other applications, it becomes critical. When an application links to a framework, it needs to know where the final resting place of that framework is going to be. The default for the framework template is ~/Library/Frameworks. Clearly this needs to change.

To get a framework to acknowledge that it will be used inside of an application, we have to set the installation directory to a relative path. That relative path is:

@executable_path/../Frameworks

Once we have the installation directory set, it is time to include it in our application.

Setting up the targets for our application

Looking in the Example project, there are two targets, one for the framework and one for the application. Opening the application’s target we can see that the framework is a dependency of the application. This will insure that the framework will be built whenever we build the application. In addition, the framework is also in the “Link Binary With Libraries” build phase so that our application can build against it.

Application Target Structure

There are also two new build steps for this application that are directly related to the framework. First, is a Copy Frameworks phase. This was added using the Project -> New Build Phase -> New Copy Files Build Phase. I then renamed it to Copy Frameworks for clarity. Selecting Show Info on the Copy Frameworks build phase will show that its directory is set to “Frameworks”. This will create a Frameworks directory inside of our application build and copy our framework into it. This is how we distribute a framework with the application.

The other additional build phase is called “Clean Framework Headers” and is a script build phase. The purpose behind this phase is to delete all of the header files out of the application bundle once everything is finished. Why? Because they are not needed at runtime and there is no point in distributing them. When we distribute the framework on its own we definitely need to leave them but for the included framework they are just wasted bits. Therefore the script removes them as the final build phase for the application only. See Code Sample 2.

echo "build path ${TARGET_BUILD_DIR}"
cd ${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}/Contents/Frameworks 
rm -rf */Headers 
rm -rf */Versions/*/Headers 
rm -rf */Versions/*/PrivateHeaders 
rm -rf */Versions/*/Resources/*/Contents/Headers

Framework Structure

For the curious, the internal structure of a framework is similar to an application. There is a compiled binary and a Resources directory that contains all of the xib, nibs, etc. There is also a Headers directory (and in some cases a PrivateHeaders directory) that contains all of the exposed header files. Where things chance is that a framework is versionable. The actual headers, resources and binary are actually stored in the Versions/${VERSION} subdirectory. From that directory there are sym-links up to the root directory of the bundle and also to the Versions/Current subdirectory.

${compiled_binary} -> Versions/Current/Example
Headers -> Versions/Current/Headers
Resources -> Versions/Current/Resources
Versions/A/${compiled_binary}
Versions/A/Headers
Versions/A/Resources
Versions/Current -> A

The purpose of this is to allow multiple versions of a framework to be stored within one bundle. However it is unlikely you will ever see more than one version in a bundle. It is a manual process to combine the versions and each version has to be kept in its own xcode project and merged by hand. In addition, since it really only makes sense to have frameworks inside of application bundles, it does not make much sense to have multiple versions of the framework. Nevertheless the functionality is there for those pesky edge cases.

Conclusion

Frameworks definitely have their place. They are very useful when writing a plugin architecture, etc. However, since they only make sense to include them in an application, unless you are sharing the framework or doing plugins, chances are it is better to just include the source code and be done with it.

xcode.png
Example Project
xcode.png
Test Project

Comments

benzado says:

You don’t have to throw away type checking just because you don’t want to expose a class. You can use the @class directive to declare the existence of an Objective-C class without exposing the whole interface. For example:

@class InternalObject;

@interface AbstractExample : NSObject {
InternalObject *object;
}

If you do this, you don’t have to expose the “InternalObject.h” file. Granted, somebody reading your header files will know of the existence of InternalObject, but giving up type checking to keep the class name a secret is kind of petty.

I use @class in all of my header files; the only time a header needs to include the header of another class is when it is a subclass of that other class.

(P.S. Logging in with openid doesn’t seem to work.)

stompy says:

For a hands on tutorial: Uli Kusterer has a video available from the Mac Developer Network:

http://www.mac-developer-network.com/videocourses/frameworks/index.html