/RXConcreteProtocol

Mix-ins for Objective-C

Primary LanguageObjective-CBSD 3-Clause "New" or "Revised" LicenseBSD-3-Clause

#RXConcreteProtocol

Back in the days before Leopard, there was a brief window during which it looked like Objective-C 2.0 would include concrete protocols, or protocols which can provide an optional implementation for some or all of their methods (Ruby, etc. folks might know these as “mixins”). Alas, this was not to be.

Fortunately, Leopard shipped with a beautifully revamped Objective-C runtime API which, among things, makes it fairly easy to introspect classes and protocols and add new methods to existing or dynamically generated classes.

RXConcreteProtocol is an implementation of this feature using the existing runtime: subclass RXConcreteProtocol, declare conformance to one or more protocols, and implement some or all of their methods. (The compiler will warn you if you don’t implement them all; RXConcreteProtocol will nonetheless function correctly in this situation.)

##How to use it

This is what you need to do to make a concrete protocol:

@protocol Fishing
-(void)catchFish:(id)aFish;
@end

@interface Fisher : RXConcreteProtocol <Fishing>
@end

@implementation Fisher
-(void)catchFish:(id)aFish; {
	…
}
@end

That’s all there is to it. You can declare conformance to multiple protocols, and they too can declare conformance to other protocols, and all of those will be added to the extended classes.

Now that you know how to make one, here’s how you extend classes with it at runtime:

[Fisher extendClass: [Bear class]];
[Fisher extendClass: [Pelican class]];

At this point, Bear and Pelican both respond to -catchFish: with the correct implementation, and both also declare their conformance to the Fishing protocol when asked:

[Bear conformsToProtocol: @protocol(Fishing)]; // → YES

Seth Delackner pointed out that when calling -catchFish: on statically-typed Bear or Pelican instances, the compiler will warn you that they don’t seem to respond to this message. This can be easily resolved by declaring the variable with id, or by declaring that it conforms to the protocol in question:

id bear = [[Bear alloc] init]; // “id” instances might respond to practically anything
id<Fishing> bear = [[Bear alloc] init]; // declare the variable as id, but conforming to Fishing
Bear<Fishing> *bear = [[Bear alloc] init]; // statically type the variable, and provide it with conformance information

By this means, you can let the compiler check your messages against both the class inheritance hierarchy and the mixed-in protocols.

##When to use it

Mixins can simplify implementation, avoiding repetition between disparate branches of your class hierarchy. In many protocol-heavy applications, there can be potential to reap this sort of benefit when for inheritance reasons you are forced to have multiple objects conform to a protocol in more or less the same way but without being able to share a common base class.

They also provide a viable alternative to inheritance when composing classes in a freeform manner, as opposed to the taxonomic approach embodied by traditional class inheritance.

##Caveats

This sort of composition is a powerful tool, but beware, it’s not without its complications:

  • Because the methods are copied into the extended classes, you won’t want to add instance variables to your concrete protocols or use them in your classes. If you find yourself desperately needing to add ivars to the extended classes—you can’t, by the way, the runtime won’t allow it—and can live with running only on 10.6, then consider looking at the new associated object API in Snow Leopard.

  • This is not the best way to add new methods to a single existing class (and its subclasses). If you want to add methods to all NSObject subclasses, say, use a category! RXConcreteProtocol is probably best applied to simplifying your own codebase (rather than, say, monkeying around with the Cocoa classes).