Uli's Web Site
[ Zathras.de - Uli's Web Site ]
Other Sites: Stories
Pix
Abi 2000
Stargate: Resurgence
Lost? Site Map!
 
 
     home | blog | moose | programming | articles >> blog

 Blog
 
 Archive
 
 Blog Topics
 

15 Most Recent [RSS]

 Less work through Xcode and shell scripts
2011-12-16 @600
 
 iTunesCantComplain released
2011-10-28 @954
 
 Dennis Ritchie deceased
2011-10-13 @359
 
 Thank you, Steve.
2011-10-06 @374
 
 Cocoa Text System everywhere...
2011-03-27 @788
 
 Blog migration
2011-01-29 @520
 
 All you need to know about the Mac keyboard
2010-08-09 @488
 
 Review: Sherlock
2010-07-31 @978
 
 Playing with Objective C on Debian
2010-05-08 @456
 
 Fruit vs. Obst
2010-05-08 @439
 
 Mixed-language ambiguity
2010-04-15 @994
 
 Uli's 12:07 AM Law
2010-04-12 @881
 
 Uli's 1:24 AM Law
2010-04-12 @874
 
 Uli's 6:28 AM Law
2010-04-12 @869
 
 Uli's 3:57 PM Law
2010-04-12 @867
 

More...

Plug-ins with Cocoa

There were a few questions and answers going around on Twitter this week regarding how one can get plug-ins to work in Cocoa. The basics are very simple, but there are one or two little pitfalls, so I thought I'd offer a few thoughts of my own:

Creating and loading a simple plug-in

The basics are very simple: The plug-ins are loadable bundles, and there's a project template for those. Every loadable bundle can have a 'main class' specified in its Info.plist. You can write code in your app that uses NSDirectoryEnumerator to walk through each folder (e.g. ~/Library/Application Support/MyApp/Plugins/, /Library/Application Support/MyApp/Plugins/ and MyApp.app/Contents/Plugins/) and uses NSBundle to load the appropriate bundle. That will make the classes in it available to your app and you can ask NSBundle for the main class of your bundle.

You can also use class methods on the class defined in your bundle or an additional plist file (or if you're careful, the existing Info.plist) to store some more information. E.g. if your plugins are for importing/exporting to other file formats, you could provide a name for the file format for the menu, the file extension to use in the save panel etc.

And finally, remember a class is just another object. You can keep an NSArray of classes.

Watch out for class name collisions

This is important if you're loading any code into an existing Cocoa app: Objective C will quietly ignore a class in a bundle that has the same name as a class it already knows. Anyone asking for that second class will get the first one. So if you provide a helper class in source code to your bundle developers, then update the source code and seed it again, one plug-in could include an older or modified version. It gets loaded, and the second plugin, relying on a newer version of that class, will fail in spectacular ways.

This becomes even harder if you're relying on popular third-party code like my UKKQueue of Rainer Brockerhoff's RBSplitView, or Andreas Mayer's AMRolloverButton. You simply can't know whether someone else might not also be using this class, or whether a revised version of the host app will start using it, and then the plugin might break.

In general, it's a good idea to prefix even third-party classes used in a plugin with your own prefix. You can for example use a #define in the precompiled header to turn RBSplitView into UKHulaHoopPluginRBSplitView. Just keep in mind that you'll have to use that name in NIBs as well.

The Fragile Base Class problem

I go into more detail about this problem in My Mac Developer Network Screencast on Frameworks, but in short, you can not add instance variables to a class in a host application that is subclassed by its plug-ins. The compiler hard-codes the size of base class instance variables and just adds its instance variables after that.

So either pad your class out with id-typed instance variables for later use so you can add some more, or put one instance variable in containing a pointer to a struct that contains what would be your instance variables. That way, the base class's size will never change.

If your application is 64-bit only, there is no fragile base class problem, as it uses a different runtime that works around this issue.

Providing access to classes in your application

An application can export its symbols, just like a library. So, with some trickery, you can have the plugins link to your application. You'll have to fiddle with the DyLd library path in your app's settings to make sure plugins linking to it will look for it in the right place (use @executable_path or @loader_path for that), and not in /Library/Frameworks or whatever, though. A tad easier than making your app the library is to have a framework inside your app that contains all the classes accessible by both the host app and the plugins.

Even easier but slightly limited would be to not allow access to the classes, and not to let your plugins subclass classes in the host application. Instead, make your plugins work more like delegates, giving them a protocol that your plug-in's main class has to implement. The advantage of this is no worrying about fragile base class, more explicit control over what symbols are 'published' to plug-ins without the need for keeping a private and public header in sync (the compiler will make sure you implement the protocol fully), and more freedom to change the internal class hierarchy of the app without a plug-in noticing.

You can use -respondsToSelector: and execute some default behaviour to make it easier on your plug-in developers, and/or pass another object that implements a particular 'host app protocol' to each plugin. The trick here is to only deal in protocols and id pointers here, which means all resolution happens at runtime, and nobody needs to link to your app to call back into it.

Reader Comments: (RSS Feed)
Clark S. Cox III writes:
Note that under the "new" runtime (i.e. iPhone and 64-bit), there is no fragile base-class problem, so the padding is unneeded there. If you do use extra variables for padding, wrap them in: #ifndef __OBJC2__ ... #endif to make sure that they are only defined on the legacy runtime.
Karsten writes:
I just wanted to point to a bundle that i wrote that can load plugins into an application. It automatically searches the user's folder, in the system's folder and in the application's folder. It's under MIT license and available on codebeach: http://www.codebeach.org/code/show/5 Karsten
Uli Kusterer replies:
Clark, thanks, I've amended the article with the general info. The __OBJC2__ constant isn't a good choice, though, as you can have that on the 32-bit runtime as well, I think (for @property and @synthesize etc.).
Harvey writes:
Clark, 64 bit definitely does not have the fragile base class problem. I believe the iPhone does still have this problem though. Can you provide a link to documentation that says otherwise? For avoiding class name collisions, this is important but super long class names are hard to read. Using a #define can work around most of this. #define RBSplitView UKHulaHoopPluginRBSplitView (You still need the @interface to use the true class name or IB will be confused.) "not to let your plugins subclass classes in the host application" This is definitely an all-around better idea. Do this if you can afford it at all. It's much less work to support.
Torsten Curdt writes:
So in other words really every private class should be prefixed? Darn - thought only the ones that get exported.
Uli Kusterer replies:
Yes, even if you don't export the symbol, they still end up in the table that NSStringFromClass and NIB loading use.
Clark Cox writes:
Uli, the macro __OBJC2__ is never defined on the old runtime. It is the correct macro for detecting whether or not code is being built for the new (i.e. 64-bit/iPhone) runtime. Harvey, no, the iPhone uses the new runtime, and does not suffer from the fragile base class problem.
Uli Kusterer replies:
Clark, the iPhone uses the new runtime. Too bad that the iPhome simulator still uses the old one.
Or E-Mail Uli privately.

 
Created: 2008-12-07 @937 Last change: 2018-04-25 @572 | Home | Admin | Edit
© Copyright 2003-2018 by M. Uli Kusterer, all rights reserved.