Recently Elsewhere
Cocoa collection subclassing | Jens/Log disagrees Faux Collection Class Subclassing | AgentM and both have some bits on class clusters. AgentM’s is a rant about not being able to easily subclass collections, which I am sympathetic too recalling the joy of programing in smalltalk with its rich collection hierarchy. Jen’s is basically a response saying Class Clusters can be frustrating but their okay, just drink the koolade AgemtM. If you explain Class Clusters to someone, they makes sense, but when you try to use Apple’s or don’t know they are there, frustrating things can happen, and I don’t think that it’s really the class clusters fault. Making a composite class by wrapping or forwarding messages isn’t that much of a hassle, subclassing anything requires some knowledge of the class and Apple does document approaches to subclass class clusters, so I’m on the koolade camp on AgentM’s rant. However, I believe there are some bugs in the implementation of a few of Apple’s class clusters that cause many to think that all Class Clusters are evil.
FUD
I think there’s a lot of fear, uncertainty, and doubt in regards to class clusters. There really shouldn’t be though, Apple has a nice page describing the benefits of Class Clusters — Cocoa Objects: Class Clusters
See a class cluster really is really just a sneaky use of a Factory Method (GOF 107) to instantiate a subclass from an abstract superclass. It’s sneaky because its hides it all. alloc, rather than actually allocating, returns a Creator (probably a singleton) and init actually allocates your real product and returns it. As demonstrated here:
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
id obj =[NSArray alloc];
NSLog(NSStringFromClass([obj class]));
NSLog(NSStringFromClass([[obj init] class]));
[pool release];
return 0;
}
2006-03-17 22:55:15.100 TestDataTypes[6105] NSPlaceholderArray
2006-03-17 22:55:15.101 TestDataTypes[6105] NSCFArray
This is all good, because the classes returned are all subclasses and being the good little object oriented programers we are, we never check what a class actually is. Instead, when need to runtime type check it’s only to determine if it’s a subclass or if it responds to a method or protocol and that should be just fine with class clusters.
If only using standard best practices when runtime type checking would keep us safe, unfortunately a few of Apple’s class clusters, mainly collection classes, are implemented in such a way that they break expected inheritance and expected polymorphism by advertising the wrong public interface for the returned private subclass (I don’t know if this was the case for NeXT or not, but I suspect it wasn’t). I’ll continue with some examples of where this implementation goes wrong.
Dumb Responses from Class Clusters with Multiple Public Superclasses
Here’s a Pop Quiz: What should this statement return?
[[NSArray array] isKindOfClass:[NSMutableArray class]];
Ideally it should return false because when we see the documentation on these public abstract classes, NSMutableArray inherits from NSArray.

However, actually this statement returns true because the private subclasses returned for both public abstract superclasses are the same class inherited from NSMutableArray known as NSCFArray.

Okay so the purpose of having the same class is for easy toll free bridging with Core Foundation, but you know, as long as the instance variables are the same, is Core Foundation really going to care about new methods or a different hierarchy in obj-c land? I’m thinking no in theory, but I’ve not tested and I’m much happier not digging into the C-ness of Obj-C and someone can prove me wrong or right if they wish. However, if you really really wanted to keep the same private class type between the two interfaces, you can always fake the expected hierarchy by making isKindOfClass: call isSubclassOfClass: on the appropriate abstract superclass’s metaclass, and really no one would be the wiser, Core Foundation or otherwise.
That was problem #1, and so you might be thinking to yourself well I don’t test for inheritance with cocoa programing often, most of the time I’ll test for specific methods out of habit, because cocoa programers tend to use informal protocols rather than having deep hierarchies anyway, so it’s not that big of a deal.
So our second pop quiz: What is the result of this statement?
[[NSArray array] respondsToSelector:@selector(addObject:)];
NSArray is not supposed to respond to addObject:. I know that, you know that, but a program is generally going to believe respondsToSelector: over what we think, and respondsToSelector: says true. Of course if you run this line:
[[NSArray array] addObject:@“test”];
you get an exception. Think about when you have a bunch of objects in a collection with various types, some may respond to addObject:, some don’t, and you want to use runtime checking to manipulate them appropriately. How are you suppose to take advantage of polymorphism of this method in this case without being able to trust respondsToSelector:.
There are simple work arounds to these problems, because they are only a handful of classes, but this is clearly a poor implementation of a class cluster, and I haven’t tested every class but I think all Mutable-NonMutable class hierarchies in Foundation on OS X suffer from this bug (Although GNUStep is A-OK from looking at the source), and I don’t think there’s a reason not to fix it, being that they are class clusters, all apple needs to modify are the private subclasses and people aren’t going to be using those methods currently because they return the wrong values that aren’t useful when wrong.
Demonstration of Problem and Fixability
I did write a few Unit Tests to clearly demonstrate this problem with NSArray. It also has a second target called ProperClassCluster that overrides some NSCFArray methods (using categories) to fix responses in inheritance & method testing. In that target I also swizzled the addObject: method to provide an example of returning the correct exception when calling a method that doesn’t exist on an NSArray. A real fix wouldn’t require these hacks, but this works for demonstration purposes. Download ProperClassCluster.zip (20k)