Safer Plugin Categories

Why did I miss last week’s post? To test this!

A few posts back I suggested the use of a category’s +load method as a way to safely swizzle methods in a plugin. What do you do, though, if the same category is going to be loaded twice?

The established behavior of categories, of course is that the last one loaded “wins”.

The behavior of classes, however, is that the first one loaded wins.

That is, if a bundle defines a class with the same name as an existing class, it is not loaded. [bundle principalClass] returns the existing class (if the principal class is the conflicted one) and [bundle classNamed:@"MyClass"] simply returns nil.

That’s good news for plugin writers who might want to use some common Cocoa-enhancing class, like RBSplitView. But how does this figure into categories?

For that, you have to remember what a category does. It adds methods to a class, replacing any that are already there. The new Leopard runtime API, however, makes it very easy to imitate this behavior dynamically:

Method _m = class_getInstanceMethod(FROM_CLASS, @selector(SEL));
class_addMethod(TO_CLASS, @selector(SEL), method_getImplementation(_m), method_getTypeEncoding(_m));

The general idea is to put the methods in a custom holding class for the time being, then copy them into the target class when then plugin is loaded. (In the Safari completion project, this code is in a macro, hence the placeholders and awkward names.) It’s easy enough to test (using, say, +instancesRespondToSelector:) if another plugin has already done this, and not overwrite existing methods.

Couple this with the usual plugin name-mangling-then-swizzle strategy to wrap an existing method with new behavior, and I’m pretty sure we’ve got a safe way to use category behavior in plugins.