The Road to Code: Custom House
Volume Number: 26
Issue Number: 01
Column Tag: The Road to Code
The Road to Code: Custom House
Creating and using custom frameworks
by Dave Dribin
Introduction
We've talked at length about the system provided frameworks, namely Foundation and AppKit. This month we're going to learn a little bit more about frameworks and how to create and use your own custom frameworks.
Libraries
In order to understand frameworks, we need to take a few steps back and talk about how source files are actually turned into an executable program. For C-based languages, including Objective-C, turning source code into a program consists of a two-step process involving tools called a compiler and linker. In the first stage, the compiler turns source files into object files. Source files have file extensions of .c for C source code or .m for Objective-C source code. Object files have .o extension, no matter what the source language is. The linker takes all the object files and combines them into the final executable, sometimes called a binary.
Figure 1: Compile and link process
Figure 1 shows how two C source files, file1.c and file2.c, are turned into an executable named program. The compiler turns file1.c into file1.o and file2.c into file2.o by a process known as compiling. The linker then combines file1.o and file2.o into the final executable named program by a process known as linking. Objective-C programs are compiled in exactly the same manner, except the source files have a .m extension.
This two-step compile and link process helps scale programs to many source files. It allows developers to split up code into multiple source files in a way that make sense for the project. This not only helps from an organizational standpoint, but helps speed up compile times. If you change one function, only the source file that contains it needs to be recompiled. All other object files can be re-used when linking the final executable.
Static Libraries
A library, in a generic sense, is a bit of code that is designed to be used and shared among many applications. Back in the early days of C programming, it became clear that there were common needs that most programs had, such as manipulating strings and handling file I/O. Instead of having each program write these from scratch every time, wouldn't it be better if you could use the same functions from application to application? This would save time and help reduce bugs.
Prior to the invention of libraries, each program wanting to share code between them had only one option: share the source files. Say we had string manipulation functions in a file named string.c, each program would need their own copy of this file. While this is fine if the shared code can fit into one file, if the shared code gets big enough to be split into multiple files, this can become unwieldy. Plus, why bother having each application compile the same source files over and over again, when they could share the object files?
Enter static libraries. Static libraries combine multiple object files into a single file called a static library. The linker can then use the static library to pull in shared code. Say we've got XML parsing code we want to share among applications, and we want to create an XML static library. First, we compile all our XML related files into a single static library. Figure 2 shows this process. The source files are compiled as usual, but instead of linking them together into an executable, a tool called the archiver combines all the object files into a static library. Static libraries have the .a extension, but they are also always prefixed with lib. Thus libxml.a is file name of the static library named xml.
Figure 2: Create static library
Using this static library is fairly easy. Figure 3 shows a program that feeds libxml.a to the linker. The linker will pull the shared XML parsing code into the executable, along with its object files.
Figure 3: Link with static library
Dynamic Libraries
While static libraries are a big improvement over manually including shared code in every project, they're not without limitations. Since the shared code is included in each of the final executables, each executable takes up more disk space. For example, if a static library is 20 megabytes in size, every executable will contain this same 20 megabytes of code, wasting disk space. Also, if the library is updated to fix a bug, each of the executables must be re-linked to pull in the new code.
In order to help combat these issues, a new kind of library was created called a dynamic library. In Windows, a dynamic library is called a dynamically linked library, or DLL, and in Linux a dynamic library is called shared library. A dynamic library has a .dyld extension on Mac OS X but the same lib prefix as static libraries. To continue our example from earlier, the XML dynamic library would be named libxml.dyld.
Dynamic libraries are linked into an application in a similar fashion to static libraries, by telling the linker about them. The big difference is that the code is not copied into the resultant executable, but a reference to the dynamic library is recorded in the final executable. When the executable is run, the operating system finds the dynamic library file and pulls the code into the program at runtime. Because of this, the executable needs the dynamic library at runtime. This is not the case for static libraries.
Because the code is not copied to the executable, the file size of the executable is smaller. However, the big benefit of dynamic libraries is that the version of the dynamic library used at runtime does not need to be the same as the version linked against. Thus, if the system includes an XML library, and it gets updated to fix a bug, you don't have to re-link to get the fixed bug in your program. It will automatically use the new library when it runs.
Frameworks
One issue with both static and dynamic libraries is that the library file only contains compiled code. The API for the library is defined in header files. The typical convention on Unix systems is to place static and dynamic libraries in /usr/lib for system installed libraries and the corresponding header files go into /usr/include. By splitting the API from the library into two separate directories, it's hard to know which header files are for which library. Also, installing new libraries must be done carefully to not clash with existing libraries.
The fine folks at Apple (well NeXT, actually) decided to utilize bundles to help solve this problem. In case you don't remember, bundles are just directories with a special file extension. For example, Cocoa applications are bundles that use the .app file extension. Figure 4 shows what the directory structure of a simple application bundle looks like. Normally, the Finder hides all this from the user, but you can see it by choosing Show Package Contents from the Finder's contextual menu. The bundle contains the actual executable in the Contents/MacOS directory, but the application can contain other resources such as nib files, images, and localized strings. Putting all assets of an application into one bundle makes it easy for users to install and remove applications.
Figure 4: Application bundle contents
Frameworks are another kind of bundle that contains a dynamic library, along with its header files. It can even contain other resources such as images and Interface Builder plug-ins. Packing all of these related files together into a single directory makes distributing and updating shared code even easier.
Almost all shared Objective-C code is distributed as frameworks. Foundation and AppKit are the frameworks we are most familiar with. But we, too, can create our own frameworks. What if we want to share code between multiple applications? Or we have some nifty code that we think others will want to use in their applications, too? We're going to cover how to create our own frameworks.
Creating a Custom Framework
We are going to walk through creating an application that uses an embedded custom framework. The application will be called Hello World and the framework will be called HelloKit. We'll also put each of these in their own Xcode project and show how to link two Xcode projects together. Let's start off by creating the framework.
Start off by creating a new project using the Cocoa Framework template and name it HelloKit. Add a new class to the project named HelloObject and update the header file to match Listing 1 and the implementation to match Listing 2.
Listing 1: HelloObject.h
#import <Foundation/Foundation.h>
@interface HelloObject : NSObject
{
}
- (NSString *)greeting;
@end
Listing 2: HelloObject.m
#import "HelloObject.h”
@implementation HelloObject
- (NSString *)greeting;
{
return @”Hello World!”;
}
@end
As you can see, this is a very simple class for demonstration purposes only. It's customary for frameworks to have a master header file with the same name as the framework that pulls in all header files for the entire framework. An example of this is the #import statement on the first line of the HelloObject.h file that includes the Foundation framework. Even though we've only got one class file right now, it's good to plan for the long term and create our own master header file. Create a new header file and name it HelloKit.h. It's going to be very simple right now, just one line of code:
#import <HelloKit/HelloObject.h>
And that's all the code we have to do for our framework. We have to mark our header files as public so that they are copied into the framework bundle. To do this, select the HelloKit target, and then change the Role of our two headers from project to public as shown in Figure 5.
Figure 5: Public headers
Before we can use this framework in another application, we need to learn a bit more about how Mac OS X locates frameworks used by an application.
Install Names
Every framework knows where it is supposed to be installed on the file system. This is called the install name and is recorded inside the framework. For example, all system provided frameworks are located in /System/Library/Frameworks, and their install name matches this. You can view the install name of a framework using the otool command line utility with the –D option:
% otool -D /System/Library/Frameworks/Foundation.framework/Foundation
/System/Library/Frameworks/Foundation.framework/Foundation:
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation
When you link against a framework, the application also remembers the install name. When the application is run, it looks for the framework where the install name says it should be. This is fine for system-installed frameworks. They'll always be in /System/Library/Frameworks. But where does our custom framework live? It can actually be in a number of places:
/Library/Frameworks,
~/Library/Frameworks,
or embedded directly into an application or other framework bundle.
Because there is no way for us to foresee all uses, and there's only one install name, we have to pick one of theses locations. What does the Xcode template use for frameworks? Open up the build settings for the HelloKit target and search for "install” as in Figure 6.
Figure 6: Default install name
As you can see, it's using our home directory. This isn't useful for us, because we want to embed this framework into our application. If we use the default install name, it won't be able to find the framework. Before I explain how to fix this, I ought to tell you that embedded frameworks go in a directory named Contents/Frameworks inside the application bundle. Thus, our Hello World application should have the directory structure shown in Figure 7.
Figure 7: Application bundle with a framework
@loader_path
Since a user can install the application anywhere, we don't want to use /Applications as our install name. Mac OS X has special keywords that provide us with the flexibility we are looking for. In Mac OS X 10.4 and later, you can use the @loader_path keyword, by changing the Installation Directory of the framework to:
@loader_path/../Frameworks
Let's parse this out. @loader_path is a special keyword that gets substituted with the path of the actual executable. For our Hello World application, this path will be:
Hello World.app/Contents/MacOS/Hello World
The .. tells Mac OS X to go up to the parent directory, Contents, and the look for a directory named Frameworks there. The final, expanded out path would be:
Hello World.app/Contents/Frameworks
@loader_path works not only for applications but for frameworks that embed other frameworks and plug-ins that embed frameworks, too.
On Mac OS X 10.3 and earlier, @loader_path wasn't available, but a keyword called @executable_path was available that pointed to the running application's executable. This worked fine for frameworks embedded in applications, but didn't allow for frameworks to be embedded into other frameworks or plug-ins. You really shouldn't use @executable_path anymore, but I wanted to explain it in case you saw it in other projects.
@rpath
The @loader_path keyword still has some limitations, however. It means you must install the framework embedded inside another application or bundle. You can't install it in the home directory, either. This typically led developers to create embeddable and non-embeddable versions of the framework. The problem is that a framework distributor doesn't know exactly where the framework will eventually be used, yet it has to chose a single installation directory. Since this is not ideal, Mac OS X 10.5 comes with a new keyword called @rpath. This keyword allows the user of the framework to decide where the framework will be installed rather than the framework distributor.
To use @rpath, set the Installation Directory to be only @rpath, as shown in Figure 8.
Figure 8: rpath install name
With this in place, we can now move on to embedding this framework into our application.
Embedding a Framework
Create a new Cocoa application project called Hello World and add a new class called HelloWorldAppDelegate. This is standard so far, but it's about to get a bit tricky, so hang in there. We'll be fiddling around with some parts of Xcode we haven't used before.
Locate the HelloKit.xcodeproj file in the Finder and drag it directly into the Groups & Files section of the Hello World project. It should be added to the list, and you should be able to see the HelloKit framework underneath the sub-project as shown in Figure 9.
Figure 9: Embedded project
With the Hello World application target selected, choose Project > New Build Phase > New Copy Files Build Phase. Change the Destination to Frameworks as shown in Figure 10.
Figure 10: Copy files phase
Next, open the disclosure triangle to the Hello World target, and rename the new Copy Files phase to be Copy Frameworks. Finally, move it between the Copy Bundle Resources and the Compile Sources build phases, as shown in Figure 11.
Figure 11: Build phase order
Now that we've got our build target set up to copy over embedded frameworks into the correct directory, we've got to use this for the HelloKit framework. We want to make sure that the framework is built before the application, so double click on the Hello World target and add the framework as a direct dependency of the application, as shown in Figure 12.
Figure 12: Adding a target dependency
Setting up the dependency builds the framework before the application gets built, but we still need to copy the framework into our bundle and link against the framework. Drag the HelloKit.framework product into both the Copy Frameworks and Link Binary with Libraries phases, as shown in Figure 13.
Figure 13: Copy and link framework
We've got to change a couple more build settings of the application, and then we're done messing around with the target. As it stands, Xcode does not know where to find the framework at compile time. We need to setup the Framework Search Paths for the application target. Again, double click on the target and add the following to Framework Search Paths:
$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)
The resulting build setting window is shown in Figure 14.
Figure 14: Framework search paths
The final build setting we need to set is the Runtime Search Paths. Remember that we used @rpath as the install name of our framework. Setting the Runtime Search Paths allows us to control what @rpath expands out to be. Since we copied the framework to our embedded Contents/Framework directory, we want to set the Runtime Search Paths to:
@loader_path/../Frameworks
The resulting build setting window is shown in Figure 15.
Figure 15: Runtime search paths
Notice that we're again using the @loader_path keyword, since that expands to the executable of our application. With all that grunt work out of the way, we're finally able to use our framework. We've set up our application target to build the framework target, copy it into our application bundle, and properly link against it. Now it's time to use this framework. Fortunately, this part is easy.
Make the HelloWorldAppDelegate implementation file look like Listing 3.
Listing 3: HelloWorldAppDelegate.m
#import "HelloWorldAppDelegate.h”
#import <HelloKit/HelloKit.h>
@implementation HelloWorldAppDelegate
- (void)awakeFromNib
{
HelloObject * hello = [[HelloObject alloc] init];
NSString * greeting = [hello greeting];
[hello release];
NSLog(@”Greeting: %@”, greeting);
}
@end
We import the master include file of the HelloKit framework at the top of the file. Then, we use the HelloObject class in awakeFromNib. Make sure an instance of HelloWorldAppDelegate is instantiated in the nib, and run the application. You should get the following output in the console:
Greeting: Hello World!
Conclusion
Whew! This is a lot of work to go through for such a simple framework, but the same steps can be applied to custom frameworks of any size. Xcode can be a fickle beast sometimes, so double-check all the steps carefully if things aren't working out. As usual, the project may be downloaded from the MacTech website, as well. If you're having issues, compare your project against the working version.
Dave Dribin has been writing professional software for over eleven years. After five years programming embedded C in the telecom industry and a brief stint riding the Internet bubble, he decided to venture out on his own. Since 2001, he has been providing independent consulting services, and in 2006, he founded Bit Maki, Inc. Find out more at http://www.bitmaki.com/ and http://www.dribin.org/dave/.