« Making Other People's Software Work for You
by Rainer Brockerhoff
(website | email)The Objective-C run-time model is notably suited for dynamically loading and executing third-party code. However, few Cocoa applications take advantage of this capability to extend their own functionality through third-party plug-ins. We'll show one of the ways of doing so.
// What's a Plug-in? Why Use Plug-ins?
These are, hopefully, rhetorical questions - you probably shouldn't be reading this paper if you don't know already. However, just to make sure, a plug-in is a way of incorporating additional functions into some software - usually an application - without having access to the application's internals.
Figure 1. Plugging in
And, of course, once you publish a practical API or plug-in interface for your application, this opens up the famous "third-party opportunities"; other programmers will publish plug-ins to extend your application, often in ways you hadn't originally envisioned. You sell more software, they sell more software, users will get more functionality, everybody gains.
// The Plugged-in Lifestyle
Before rushing out to design a plug-in API, we need to discuss what sort of application you want to plug into. If it is a Carbon application, your best bet is to use CFPlugins. As you may have gathered from the title of this paper, CFPlugins are beyond the scope of this discussion1.
If you have a Cocoa application or, better still, are starting off from scratch with a new Cocoa application, we'll look at one quick and easy way of implementing plug-ins with NSBundles.
Let's suppose a rather common scenario: you have a document-based application, or at least a main window that acts upon some set of data. You wish to allow plug-ins that act upon this set of data, or upon those documents, and report their results in separate views inside the appropriate window.
For illustration, we'll look through a simplified sample pair of application and plug-in. Full source code and Project Builder projects should accompany this paper; if not, you can download everything from my site: http://www.brockerhoff.net/pap.html.
// Inside the Sample Code
Here's what should be inside the downloaded folder:
Figure 2. Inside the sample
The first pair of icons are the complete project folder for the sample plug-in, and a link to the project itself. The second pair does likewise for the sample application.
After that comes a copy of this paper, and a header file for the plug-in interface API.
Before discussing the API itself, let's first look at the plug-in project.
// The Plug-In Project
In our approach, a plug-in is a full-fledged Cocoa bundle. Select "Cocoa Bundle" from Project Builder's "New Project" dialog.
Your first step, after getting the new project, should be to edit the project's target, click on the "Build Settings" tab, and change the bundle's extension in the WRAPPER_EXTENSION line. The default is "bundle", but I recommend changing this to "plugin"2, or even to a private extension for your application; in the latter case, you can twiddle your application's bundle settings to define a general icon for your plug-ins.
Also in the bundle settings, go to the "Cocoa Specific section" and fill out the "Principal class" line with the name of your plug-in's class; you'll learn what this means further down.
As a bundle, your plug-in can include nib files, resource files, help files, icons, and all the other paraphernalia; everything will be easily accessible through the NSBundle APIs.
// The Application Project
The sample application is set up to copy the compiled sample plug-in from the plug-in project's build folder into the application's plug-in folder; see the target's "Files & Build Phases" tab in the "Copy Files" phase. Although this is convenient for testing, you may not want to do this for a real application.
// Plug-in API Goals
A viable Cocoa plug-in API has to meet several conflicting goals, such as:
- Be simple and easy to use (but powerful);
- Give a plug-in access to all data it needs (but restrict access to everything else);
- Keep both application and plug-in ignorant of each other's implementation details;
- Allow any number of plug-ins to work simultaneously without interfering with each other.
// A Disclaimer on Style and Contents
Perhaps to the discomfort of some purists, I will use the phrase "method doSuch of someObject is called" instead of the more O-O-O3 "message doSuch is sent to someObject". This is to make it more understandable to programmers who are new to Cocoa but have some C experience, as well as to allow me to use shorter descriptions in several places.
Most, but not all, code from the sample projects will be explained in some detail below. The omitted lines are not directly germane to our plug-in subject. The sample projects do not pretend to be stylistical models for a canonical Cocoa application; things such as some accessor methods are missing, while some superfluous error-checking is done just to show where a "real" application might do so.
You're free to use the sample code for your own purposes, as long as you assume all responsibility; if you do so, I'd appreciate a short mention in your software's documentation or "About" box.
// A Sample Plug-in API
Here's the API from our sample. This is essentially the PluginInterface.h header file, and contains everything the plug-in project needs to know:
@protocol PAPluginProtocol + (BOOL)initializeClass:(NSBundle*)theBundle; + (void)terminateClass; + (NSEnumerator*)pluginsFor:(id)anObject; - (NSView*)theView; - (NSString*)theViewName; @end
This is an Objective-C formal protocol definition. A plug-in's principal class must adopt this protocol in order to be accepted by the application. The application will communicate with the plug-in only through these methods, but all of them must be implemented by a plug-in.
In a real application you could also define informal protocols, containing methods a plug-in may (or may not) implement.
// The Application's Standpoint
To explain exactly what happens, we'll look at it from the application's standpoint.
Our sample application has a main window which is opened after application initialization. The window can be closed and reopened, but to simplify matters we allow only one window to be open at a time. The main window contains a tab view which will be populated by the plug-ins.
All of the action happens in the application's delegate, an instance of the PADelegate class.
// Starting Up
The PADelegate's init method is the first to be called when the application starts up, immediately after loading the application's main nib file.
- (id)init { self = [super init];
The pluginClasses variable will hold an array of all plug-in classes.
pluginClasses = [[NSMutableArray alloc] init];
The pluginInstances variable will hold an array of all plug-in instances.
pluginInstances = [[NSMutableArray alloc] init]; return self; }
Why are there two arrays? The array of classes points at all loaded plug-ins. However, to do any useful work, a plug-in class has to be instantiated as an object and this object has to be matched to the data it will operate on.
In a real application, the pluginClasses array would continue to be attached to the application delegate, but the pluginInstances array would belong to a document, or a window, or to the controller object for the data those plug-in instances are operating on.
// Qualifying Plug-Ins
The following method is called while the application's icon is still bouncing in the dock, and is the logical place to locate, qualify and load plug-ins.
- (void)applicationWillFinishLaunching: (NSNotification*)notification {First we get the path to the built-in plug-ins folder, inside the application at /Contents/Plugins:
NSString* folderPath = [[NSBundle mainBundle] builtInPlugInsPath];If the folder exists, we make an enumerator of all items inside it which have the requisite "plugin" extension:
if (folderPath) { NSEnumerator* enumerator = [[NSBundle pathsForResourcesOfType:@"plugin" inDirectory:folderPath] objectEnumerator];Now, we'll loop over all candidate plug-ins and try to activate them:
NSString* pluginPath; while ((pluginPath = [enumerator nextObject])) { [self activatePlugin:pluginPath]; } } }
In a real application, you might also want to loop over the contents over other folders, such as a subfolder of ~/Library/Application Support/.
// Activating a Plug-In
Now let's see what happens inside the activatePlugin: method. First we try to get a NSBundle from the path argument:
- (void)activatePlugin:(NSString*)path { NSBundle* pluginBundle = [NSBundle bundleWithPath:path];
If it's a valid bundle, we try to read its info.plist file:
if (pluginBundle) { NSDictionary* pluginDict = [pluginBundle infoDictionary];
Next we try to get the name of the bundle's principal class from the dictionary:
NSString* pluginName = [pluginDict objectForKey:@"NSPrincipalClass"];
If the name exists, we have to check if it duplicates an already known name:
if (pluginName) { Class pluginClass = NSClassFromString(pluginName);
Class names are global to Objective-C applications; so if the name is already registered, we'll skip this plug-in. Plug-in writers have to make sure they're using unique names by prefixing them with company or product abbreviations, for instance. So, if the name was unknown up to now, we can use it:
if (!pluginClass) { pluginClass = [pluginBundle principalClass];
This last line loads the bundle's code and does all the necessary registration with the run-time libraries. However, we still have to see whether this plug-in is our kind of plug-in. It has to conform to our formal protocol:
if ([pluginClass conformsToProtocol:@protocol(PAPluginProtocol)] &&
and it should be a subclass of NSObject:
[pluginClass isKindOfClass:[NSObject class]] &&
and it should successfully initialize itself:
[pluginClass initializeClass:pluginBundle]) {
and if it passes all these tests, we consider the plug-in qualified, loaded and initialized and add it to our pluginClasses array:
[pluginClasses addObject:pluginClass]; } } } } }
The initializeClass: method call is the first one defined in our plug-in protocol. Note that it is a class method; we're not instantiating an object at this point. This method's argument is the plug-in's bundle; since we already have it at this point, it's more efficient passing it as an argument, rather than having the plug-in find it again.
The plug-in class should, at this point, perform any global initializations such as checking its environment, preloading icons and preferences, and so forth. initializeClass: should return YES if everything's OK and NO if it fails to initialize correctly. Let's skip to our sample plug-in's implementation for a moment:
static NSBundle* pluginBundle = nil; @implementation AppPlugin + (BOOL)initializeClass:(NSBundle*)theBundle { if (pluginBundle) { return NO; } pluginBundle = [theBundle retain];< return YES; }
The sample plug-in checks whether it already has been initialized; if not, it just stores the bundle reference in a global, retains it for form's sake, and returns YES.
To jump ahead for a moment, there's a counterpart method of initializeClass: called terminateClass:
+ (void)terminateClass { [pluginBundle release]; pluginBundle = nil; }
This is called when the plug-in won't be needed anymore, just before the application terminates, and should close or release all resources opened or retained by initializeClass:.
At present, Cocoa doesn't allow unloading NSBundles, supposedly because of threading issues. Should that capability be added in the future, the application would unload the plug-in just after calling terminateClass.
// Instantiating a Plug-In
Now that we have all our plug-ins loaded and initialized, the application can go on with its business. In due course, the application's main window will be opened. This happens here:
- (BOOL)applicationOpenUntitledFile: (NSApplication*)sender {
We use theWindowController as a flag to check whether the main window is already open. If it isn't, we try to create a new NSWindowController:
if (theWindowController==nil) { theWindowController = [[NSWindowController alloc] initWithWindowNibName:@"MainWindow" owner:self]; if (theWindowController) {
The next line connects the IBOutlets:
[theWindowController window];
We now check the IBOutlets to make sure, and set up to loop over all plug-in classes:
if (theWindow&&theTabView) { NSEnumerator* enumerator = [pluginClasses objectEnumerator]; Class pluginClass;Interface Builder won't let us create an empty tab view, so first we remove all existing tab items:
while ([theTabView numberOfTabViewItems]) { [theTabView removeTabViewItem: [theTabView tabViewItemAtIndex:0]]; }
Now we loop over all plug-in classes and ask each one to instantiate itself:
while ((pluginClass = [enumerator nextObject])) { [self instantiatePlugins:pluginClass]; }
The window is now ready for display, so we show it and return YES:
[theWindow makeKeyAndOrderFront:self]; return YES; }
We'll get to this point only if there was something wrong with the nib file or the outlets, so we'll release the window controller and return NO:
[theWindowController autorelease]; theWindowController = nil; } } return NO; }
Let's look at the method we're calling in the inner loop above. We start out with a call to the plug-in class itself:
- (void)instantiatePlugins:(Class)pluginClass { NSEnumerator* plugs = [pluginClass pluginsFor:theWindow];
This returns an enumerator for all plug-in objects the plug-in wants to instantiate (later on, we'll see why this is a Good Thing). The argument could be any object that the plug-ins are supposed to operate on; in a real application, it might be a file, a document, or an image. Here we pass the window itself as nothing else is handy. Then we loop over all objects:
NSObject<PAPluginProtocol>* plugin; while ((plugin = [plugs nextObject])) {
For each object, we add a new tab to the tab view:
NSTabViewItem* tab = [[[NSTabViewItem alloc] initWithIdentifier:nil] autorelease];
We autorelease it so it will be released whenever its tab item is released. Now we'll ask the plug-in instance to pass us the NSView it wants to show in the tab item:
NSView* view = [plugin theView];
We ask the tab view for its content size, resize the plug-in's view to fit that, and insert it into the tab item:
NSRect frame = [theTabView contentRect]; [view setFrame:frame]; [tab setView:view];
We ask the plug-in instance for the name it wants to show in the tab, and add the tab item to the tab view:
[tab setLabel:[plugin theViewName]]; [theTabView addTabViewItem:tab];
And finally we add the plug-in instance to our pluginInstances array:
[pluginInstances addObject:plugin]; } }
At this point, it may be instructive to run the sample application and see how the main window looks.
Figure 3. The main window
In our example, we have only one plug-in bundle (and class), but it generates two plug-in instances, resulting in two tabs in the main window; one with text, as shown in Figure 3, and one with an image.
// Many Plug-Ins in One
As we've seen, the pluginsFor: method returns an NSEnumerator, meaning that more than one plug-in instance may be returned. There are several reasons for allowing this.
First of all, a plug-in author may simply package several unrelated plug-ins into the same bundle. Also, the same plug-in might want to return several views relating to the same object - the parameter passed to pluginsFor:. For instance, you might pass an object representing an HTML file and get a view showing the raw HTML, and another one showing rendered HTML.
Finally, the plug-in class may test the object you're passing and decide to return one, another, or several views depending on its type; or the application could pass an NSArray of objects and expect the same number of plug-in instances back.
// Pulling the Plug
To conclude our sample application, let's see what happens when the main window is closed:
- (void)windowWillClose:(NSNotification*)notification {
First we check to see if it's the main window that will close:
if ([notification object]==theWindow) {
If so, we clear the window pointer and release the pluginInstances array:
theWindow = nil; [pluginInstances release]; pluginInstances = nil;
and we also release and clear the window controller:
[theWindowController autorelease]; theWindowController = nil; } }
At this point we're back to where the application was before the main window was opened.
Let's now see what happens before quitting the application:
- (void)applicationWillTerminate:NSNotification*)notification { NSEnumerator* enumerator; Class pclass;
First we make sure the main window is closed (if it already is, theWindow will be nil):
[theWindow close];
Then we loop over all plug-in classes and call terminateClass for each one, to give each plug-in class a chance to clean up and close or release resources:
enumerator = [pluginClasses objectEnumerator]; while ((pclass = [enumerator nextObject])) { [pclass terminateClass]; }
And finally we release the pluginClasses array:
[pluginClasses release]; pluginClasses = nil; }
This concludes our walk-through of the sample application. There's some extra (trivial) housekeeping code; please refer to the actual sample source code to see details.
// The Plug-In's Standpoint
We've already looked at the sample plug-in's initiateClass: and terminateClass methods. Now we'll have a look at rest of our plug-in, especially the all-important pluginsFor: method.
In a very simple case, this method wouldn't even be necessary; you could just call [[thePluginClass alloc] init] and presto, you'd have a single plug-in instance. Or even more simply, if the plug-in is needed only for computations and has no user interface at all, this computation might be implemented by some class method of the plug-in class itself, so no instance would be needed!
However, we're more ambitious here. Remember that our sample application contains the call:
NSEnumerator* plugs = [pluginClass pluginsFor:theWindow];
and this will return a number of plug-in instances which the plug-in consider appropriate for the parameter object. (Or it might return nil if the object is completely inappropriate for that plug-in.) After obtaining the enumerator, we loop over all plug-in objects with:
NSObject<PAPluginProtocol>* plugin; while ((plugin = [plugs nextObject])) { // store plugin somewhere< }
Notice the declaration; we only know - indeed, we only need to know - that each returned plug-in instance is some kind of NSObject and that it will obey our PAPluginProtocol. Within these constraints, the pluginsFor: method is free to return any sort of object.
This is a perfect opportunity to implement a class cluster on the plug-in side. A class cluster is a way of declaring and using objects which have the same external interface, but may have wildly different internals. For instance, the NSNumber class is in reality a cluster; depending on how you create it, it may contain an int, a BOOL, a float or several other data types. NSString is another common instance of a class cluster.
// How to Make a Little Plug-In
Let's look at the internals of our sample plug-in. First we declare our bundle's principal class:
@interface AppPlugin:NSObject<PAPluginProtocol> { NSString* theViewName; id theObject; } - (id)initWithObject:(id)anObject name:(NSString*)name; @end
Thanks to the leniency of Objective-C, we don't need to repeat the various methods of the PAPluginProtocol. We're just declaring two instance variables, theViewName and theObject, which are common to all subclasses, and the designated initializer initWithObject:name:.
Next we declare two concrete subclasses:
@interface AppImagePlugin:AppPlugin { IBOutlet NSView* imageView; } + (AppImagePlugin*)pluginFor:(id)anObject; @end @interface AppTextPlugin:AppPlugin { IBOutlet NSView* textView IBOutlet NSTextView* actualText; } + (AppTextPlugin*)pluginFor:(id)anObject; - (void)appendText:(NSString*)text; - (IBAction)nowAction:(id)sender; @end
These subclasses are the ones which are actually instantiated and passed to the calling application. Each one declares instance variables necessary to its function, and the pluginFor: class method which creates an instance for that subclass. All will become clear when we look at the principal class' pluginsFor: method.
First of all, it creates an autoreleased empty array which will be filled with plug-in instances.
+ (NSEnumerator*)pluginsFor:(id)anObject { AppImagePlugin* imgp; AppTextPlugin* txtp; NSMutableArray* plugs = [[[NSMutableArray alloc] init] autorelease];
Next, we ask the AppTextPlugin subclass for an instance of itself, passing it the parameter object. If the subclass doesn't like the object, it will return nil; otherwise, we'll add the instance to the plug-in array:
if ((txtp = [AppTextPlugin pluginFor:anObject])) { [plugs addObject:txtp]; }
Now we'll do the same for the AppImagePlugin subclass:
if ((imgp = [AppImagePlugin pluginFor:anObject])) { [plugs addObject:imgp]; }
If there's something in the plug-in array we'll return an enumerator for it, otherwise we'll return nil:
return [plugs count]?[plugs objectEnumerator]:nil; }
The array, its enumerator, and the plug-in instances themselves are all autoreleased, so they'll be deallocated unless the calling application retains them.
We also need common initialization and deallocation methods for our class cluster. The designated initializer initWithObject:name: method initializes the instance variables, retaining the view name and parameter object:
- (id)initWithObject:(id)anObject name:(NSString*)name { self = [super init]; theViewName = [name retain]; theObject = [anObject retain]; return self; }
And the dealloc method releases them:
- (void)dealloc { [theViewName release]; [theObject release]; [super dealloc]; }
There remain two accessor methods which are required by our protocol. The first one is just a dummy method, and returns nil, because there'll never be an actual instance of the general AppPlugin class. The subclasses will always override it.:
- (NSView*)theView return nil; }
The other accessor method is common to all subclasses:
- (NSString*)theViewName { return theViewName; }
And that's all there is to the AppPlugin class. Now let's look at the subclasses.
// A Simple Concrete Plug-In
The AppImagePlugin subclass is very simple. It just shows a fixed image in its view. First, we implement dummy class methods:
+ (BOOL)initializeClass:(NSBundle*)theBundle { return NO; } + (void)terminateClass { }
These will never be called by well-behaved applications, but are here just for form's sake. Now we'll finally get to make an actual plug-in instance. Just watch:
+ (AppImagePlugin*)pluginFor:(id)anObject {
First we create and initialize an autoreleased instance:
AppImagePlugin* instance = [[[AppImagePlugin alloc] initWithObject:anObject name:@"Image"] autorelease];
Next, we check if it really exists:
if (instance &&
and then, try to load the nib file with the image view:
[NSBundle loadNibNamed:@"ImageView" owner:instance] &&
and finally check if the view's outlet was connected:
[instance theView]) {and if all went well, we return the instance:
return instance; }
otherwise we return nil to indicate failure:
return nil; }
To complete the subclass, we need to implement the dealloc instance method, which just releases the view it owns:
- (void)dealloc { [imageView release]; [super dealloc]; }
and also the accessor for the view itself:
- (NSView*)theView { return imageView; }
// Now for Something More Complex
The other subclass, AppTextPlugin, is similar, but has a little more complexity thrown in to show what can be done. It implements essentially the same methods we've seen above (but substituting Text for Image wherever appropriate), and some additions. The pluginFor: method:
+ (AppTextPlugin*)pluginFor:(id)anObject { AppTextPlugin* instance = [[[AppTextPlugin alloc initWithObject:anObject name:@"Text"] autorelease]; if (instance && [NSBundle loadNibNamed:@"TextView" owner:instance] && [instance theView] &&
is the same up to this point, where we check the actualText outlet directly (we could have implemented an accessor for it, of course):
instance->actualText) {
and here, where we append a description of the parameter object to the text:
[instance appendText:[anObject description]]; return instance; } return nil; }
We also implement the appendText: method as:
- (void)appendText:(NSString*)text { [actualText setSelectedRange:NSMakeRange([[actualText string] length],0)]; [actualText insertText:text]; }
This inserts the parameter string at the end of the existing text, just to show that the plug-in instance can operate on subviews.
Finally, we define the action method for the view's "Now" button:
- (IBAction)nowAction:(id)sender { [actualText setString:[[NSCalendarDate calendarDate] description]]; }
This will change the displayed text to the current date and time, just to show that the view's actions will be handled by the plug-in instance.
// Back to the Real World
So, with very little effort, we can implement plug-ins for Cocoa applications. Hopefully you'll be able to expand our little sample application/plug-in for use in real-world applications; this shouldn't be too hard as long as you pay careful attention to the conceptual aspects.
My first Cocoa application, XRay4 , uses a more complex version of the described scheme. The official plug-in API ought to be published soon - writing this paper showed me several points where it should be simplified - but I'll go over some of its extra functionality, to show you what you might want to do in your application.
XRay's plug-ins operate on file system entities. The application itself just handles the details of multiple documents (one per window), registration and on-line documentation, and a window header with basic information like name, path and icon. Everything else is handled by plug-ins. Here's a typical XRay window:
Figure 4. An XRay window
The upper, somewhat darker, portion of the window is fixed; the lower is switched between the several views provided by the plug-ins. Switching is done by a pop-up menu, as there may be more plug-ins than would fit into a tab view.
The first difference from our sample is that XRay has a built-in plug-in class. This is coded exactly as if it were an external plug-in, except that the portions relating to activating an external bundle are skipped.
Since we're operating on file system entities, the parameter object which is passed to plug-ins is a special object which serves two functions: it already stores all necessary information about the file or folder in its instance variables, and it provides methods which allow the plug-in limited access to the corresponding document.
The first aspect means that details about obtaining and saving file metadata are handled by the application's document class, and are transparent to the plug-ins. On the other hand, if you change any of the editable fields handled by the plug-in, it needs to inform the document that it is "dirty" and can be saved; that's what the callback methods in the parameter object are for.
This also means that, when a plug-in class is called to instantiate a plug-in, the class can test what type of file or folder it is receiving. In XRay, the built-in plug-ins use this to change the items in the pop-up menu; in Figure 4, the first one reads "Package Information", but it would read "File Information" if the item were a plain file. The item type is also used to suppress certain plug-in subclasses when they're inappropriate; for instance, when opening a volume, the "Type, Creator & Extension" plug-in would not be instantiated.
In Figure 4, the button with the two green arrows starts a separate thread that counts all contained items in a folder or package. This can take a long time for large folders, and the view will show intermediate results while counting; meanwhile, the user may want to switch to another plug-in, and switch back later. While this is going on, some of the editable fields may be selected and modified, or the user may switch to another application. Also, there are plug-ins with buttons which resize their views, meaning that the document window may also be resized, and other plug-in's views may be resized with it.
All this is handled by some complex juggling of events, threads, and callback methods. XRay plug-ins must also implement some predefined ways of declaring view names, plug-in icons, on-line help files, preferences, and so forth. In order to make this easier for the plug-in writer, a plug-in isn't handled directly as in our sample application, but rather through a special plug-in controller class, which acts as middleware to implement these features.
Preferences, in particular, are handled by inserting each plug-in's preferences as a NSDictionary into XRay's own preference hierarchy. The application reads default values from the plug-in's info.plist file. Setting preferences is handled by producing separate preference views (and corresponding plug-in instances) for each plug-in class. There are separate callbacks to allow each plug-in access to its own preferences and to XRay's global preferences.
// Reliability and Security
One of the first thing one learns about computers is that anything you plug into something else must be compatible both physically and electrically. The same is true for plug-ins and applications. Objective-C allows us to define, using protocols and header files common to both plug-in and pluggee, a degree of compatibility at compile-time - so that the compiler doesn't complain - and at run-time, by checking for protocol conformance and bundle validity.
However, neither compile-time nor run-time checks are completely reliable due to the extremely dynamic nature of the Objective-C run-time model.
The compile-time check may fail (or, even worse, succeed erroneously) if the plug-in writer has an obsolete or reverse-engineered header file. In any event, a plug-in may compile perfectly and fail spectacularly (or, even worse, insiduously) at run-time.
The run-time check we describe in the sample code is rudimentary; in essence, we're just checking that the protocol name the plug-in writer used is the correct one. The actual implemented methods may be incorrect. One could, of course, check before each call to see if the plug-in really implements that particular method, and do similar checking on the types of returned objects; the resulting performance hit may not always be acceptable.
Regarding security, protecting an application against a malicious plug-in is impossible. Since Objective-C classes live in an application-wide namespace, this means that all your application's private classes are accessible to all plug-ins - and of course, vice-versa! Also, since plug-ins can freely use any system frameworks, a malicious plug-in could do a lot of mischief.
Unfortunately, at this time I must leave any solutions to these problems as an exercise for the interested reader...
// Conclusion
Writing this paper was extremely instructive, as it forced me to pare my existing (and at places, overly complex) plug-in scheme down to bare essentials. Writing and debugging the sample application also showed me why my plug-ins were leaking memory. The details were much more obvious and easy to understand in a more restricted setting.
Hopefully you've learned as much from reading this paper as I did from writing it. Don't hesitate to e-mail me at rainer@brockerhoff.net if you have any questions or suggestions, and check my site at http://www.brockerhoff.net/pap.html for the sample code, errata and other papers.
// Acknowledgements
This paper was first published at MacHack 2002.
Many thanks to Mike Beam, who reviewed an early draft of this paper, and to the folks on the various Mac OS X and Cocoa lists who helped me up the slippery initial slopes of the Cocoa learning curve. Special thanks to Erik J. Barzeski for helping reformat this paper for publication at Cocoa Dev Central.
// Footnotes
- "Beyond the scope of this paper," along with the immortal "space precludes," means I don't know much about them myself.
- In some Project Builder versions I found it necessary to close and reopen the project for the extension change to "stick"; changing the extension after building even once may encounter even more resistance from Project Builder.
- "Orthodox-Object-Oriented"
- <plug>XRay is $10 shareware and can be downloaded from my website at http://www.brockerhoff.net/xray/. </plug>
One of the code sections bleeds over into the rest of the document, at least in Safari. Very annoying.
Posted by: sinclair44 on March 5, 2003 08:06 PMfixed
Posted by: Erik J. Barzeski on March 5, 2003 09:21 PMThis is very informative and helpful. Thanks!
Posted by: Brent Gulanowski on June 9, 2003 10:46 AMwww.brockerhoff.net is not functional.. i am unable to download the source or view additional material
Posted by: unlisted on June 10, 2003 07:49 AMThis bit of code:
[pluginClass isKindOfClass:[NSObject class]]
Generates a warning:
warning: `Class' may not respond to `+isKindOfClass:'
Posted by: Matt on November 8, 2003 07:14 PM