oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Programming With Cocoa Plug It In, Plug It In

by Michael Beam

If you've spent much time reading up on Cocoa and Objective-C, you've probably heard that Objective-C is a dynamic language. Ezra Epstein evangelizes this advantage of Objective-C in two excellent columns worth your time to read. In this column, we're going to talk a bit about Objective-C and how to implement a plug-in architecture for ImageApp.

When an application is compiled, it is linked to certain frameworks required by that application. Most of the time the frameworks we link to are the Foundation framework and the Application Kit. When an application is launched, every class in each linked framework is loaded into the Objective-C runtime for that application. So when we launch an application like ImageApp, all the classes, protocols, and categories for the Foundation Framework, AppKit, and ImageApp itself are loaded into the Objective-C runtime. These ideas are elaborated upon in Ezra Epstein's articles mentioned previously.

Now, it used to be that once an application was launched, and the code it needed to run was loaded into memory, that was the end of the story. Needless to say, the inability to load new code into memory and integrate it with the application already running is somewhat limiting. In Objective-C, the ability to load new classes into the runtime is a painless process. The Objective-C runtime relies on the function objc_addClass to load new class definitions into the runtime for an application. We won't detail the use of this function here, but you can read more about it and the Objective-C runtime in Apple's Inside Mac OS X: The Objective-C Programming Language (formerly Object-Oriented Programming and the Objective-C Language).

Our purpose today is to learn a bit about the practical benefits of these features of Objective-C as they are implemented in Cocoa. In Cocoa, this behavior is encapsulated in the class NSBundle, found in the Foundation Framework (with extensions that support nib loading defined in the AppKit). NSBundle is a class that represents a bundle in the file system and provides an interface to loading the code and resources contained within the bundles. This class is the basis of Cocoa's plug-in architecture, and we will spend a good part of the column seeing this class in action.

A Bit About Bundles

Download example files for this article here.

Before we can get to the nitty-gritty code, we have to learn more about bundles, a central feature of Mac OS X. A bundle is a directory structure in the filesystem that stores in one location binary executables or other compiled pieces of code, along with any associated configuration files, localization files, and resources, such as images, sounds, and nibs. Bundles are omnipresent in Mac OS X. Applications are bundles with .app extensions; frameworks are bundles with a .framework extension; system menus are bundles with a .menu extension, and preference panes and screensavers are bundles as well.

Often the Finder presents bundles to the user as a single file. This is true with applications and menu items bundles. This has the advantage of insulating the user from the nuts and bolts and resources used by an application, stuff most users don't really care about, but let people like us dork out by opening nibs we shouldn't be and modifying images and other resources in the name of customization. To learn more about bundles and how they are used in Mac OS X, you should read about them in the Mac OS X system overview: Inside Mac OS X: System Overview.

Related Reading

Learning Cocoa with Objective-C
By James Duncan Davidson, Apple Computer, Inc.

Now, what does this have to do with plug-ins? Well, plug-ins in Cocoa are nothing more than bundles. Usually these bundles have a .plugin extension, but .bundle is commonly seen as well. Using NSBundle it is then possible to locate a plug-in bundle, and register with the Objective-C runtime the compiled classes contained within the bundle. However, for this to work well, you have to set up something of a plug-in architecture for your application, which is exactly what we're going to do today.

This will be done in two steps. First we will create an interface to our plug-in system and learn how to create a plug-in bundle, and second we will add support into ImageApp to discover installed plug-ins and make them useable by the user.

In the last column before we took our AddressBook break, we learned how to make an image filter class that converts a color image into a grayscale image. Filters are a good candidate for implementing as plugins for lots of reason. First of all, they are relatively simple: put some data in, let the filter do its thing, get some data out. Interaction with the parent application is pretty minimal. Additionally, there are a wide range of image manipulation operations that can be accomplished with filters, and many people may have a need for a special filter to fill in some specific functionality not included in the original application. Here plugins allow developers and advanced users of your application to develop modules that fill in special functionality.

To this end we will be creating a plug-in architecture for ImageApp. This will involve defining some standard that describes to developers what the plug-in socket looks like. We will also have to create a plug-in manager within ImageApp that searches for plug-ins and makes them accessible in the user interface.

A Public Interface

If other developers are to successfully develop plug-ins for your application, you must publish some interface or specification to your system. With Objective-C, we have a couple of options available to accomplish this task. One way of publishing your plug-in system is to create a formal protocol. Plug-in developers would then create a plug-in by creating a class that conforms to this formal protocol.

Another option--the one that we will implement here--is to create a public class interface, which plug-in developers would then subclass. With our filter plug-in we don't need to do much other than make public the method filterImage:. If you recall from the column where we made the grayscale filter, this method takes an NSImage, applies some filtering operations on it, and returns a new NSImage. This was all done in the class IAGrayscaleFilter. Today we will make an interface to the class IAFilter which defines the method filterImage:. We will then have IAGrayscaleFilter inherit from this class. The purpose of IAFilter is to create a common point of communication between us, the developers of ImageApp, and any other developers who would like to create an image filter plug-in.

Our first step is to create a new class, IAFilter. Do this now from the File menu, choosing to create a simple Objective-C class. Name it IAFilter. Add to IAFilter.h the single method declaration for filterImage:

- (NSImage *)filterImage:(NSImage *)srcImage; 

This is the same method that we declared previously in IAGrayscaleFilter.

By default, the header for the Foundation framework is imported into our class. However, we include NSImage as part of our class declaration, so we need to let the compiler know about that. We'll do that by replacing Foundation and Foundation.h with Cocoa and Cocoa.h in the import statement.

As part of the public interface we will also declare a convenience constructor for all filters, filter. Do this now in IAFIlter.h as follows:

+ (IAFilter *)filter;

The implementation for filter is simple. Its only job is to alloc and init an instance of IAFilter, or a subclass of IAFilter depending on the receiver of the filter message in ImageApp. In keeping with the conventions of Cocoa memory management, we will autorelease this instance of IAFilter just before returning it, as filter is no more than a convenience constructor. Clients should always assume that convenience constructors return an autoreleased object. The filter method looks like this:

+ (IAFilter *)filter
    return [[[self alloc] init] autorelease]; 

Notice that the alloc message was sent to self, not IAFilter. This ensures that in situations when the client is instantiating a subclass of IAFilter, that the alloc and init methods of the subclass are invoked, rather than those of IAFilter. It may be that the subclass has no implementation of its own for alloc and init, and relies instead on the superclass's implemenation of these methods, but if they were there, they would be used.

Notice that the initial alloc message was sent to self, not IAFilter. This ensures that in situations when the client is creating a subclass of IAFilter, that the alloc and init methods of the subclass are invoked, if the subclass did indeed override one or both of those methods.

IAFilter's implementation of filterImage: will be to simply return the image passed to it, like so:

- (NSImage *)filterImage:(NSImage *)srcImage 
    return srcImage; 

Note that the filterImage: method is an instance method rather than a class method. Given the implementation of filterImage: that we explored in the previous column, we could have done the same thing we did there in a class method. The reason for this is that we never accessed any instance variables in the filter object. That is one of the key differences between class and instance methods. Class methods may not access instance variables, while instance methods may.

In this situation, when filterImage: does not access instance variables, it is a strictly processing method. That is, it takes input, does something to it, and spits the result back out to the sender. However, by making filterImage: an instance method here, we give subclassers the flexibility to declare and interact with instance variables, which would not have been possible had filterImage: been a class method. This flexibility makes it possible for developers to create plug-ins that have user interfaces built with Interface Builder.

We now have a class, IAFilter, that people can use to hook into the ImageApp plug-in system. The next thing to do is take the class IAGrayscaleFilter out of ImageApp, and put it into its own bundle. Before we get into that, we must do one last thing to IAGrayscaleFilter, and that is to make it a subclass of IAFilter, rather than NSObject. Do this now by replacing NSObject in the @interface line with IAFilter. Make sure you import IAFilter.h into the IAGrayscaleFilter header.

Pages: 1, 2, 3

Next Pagearrow