Welcome to another chewy edition of Friday Q&A. This week, Gwendal Roué has suggested talking about the techniques of subclassing class clusters.
Abstract Classes
To subclass a class cluster, you need to know what it is, and to understand class clusters you must first understand the concept ofabstract classes. It's an easy concept, though.
Anabstract class is a class which is not fully functional on its own. It must be subclassed, and the subclass must fill out the missing functionality.
An abstract class is not necessarily an empty shell. It can still contain a lot of functionality all on its own, but it's not complete without a subclass to fill in the holes.
Class Clusters
A class cluster is a hierarchy of classes capped off by a public abstract class. The public class provides an interface and a lot of auxiliary functionality, and then core functionality is implemented by private subclasses. The public class then provides creation methods which return instances of the private subclasses, so that the public class can be used without knowledge of those subclasses.
TakeNSArray as an example. It's an abstract class which requires its subclasses to provide implementations of thecount andobjectAtIndex: methods. It then provides a bunch of methods built on top of those two, such asindexOfObject:,objectEnumerator,makeObjectsPerformSelector:, and many more.
The core functionality is then implemented in private subclasses such asNSCFArray. TheNSArray creation methods such as+arrayWithObjects: or-initWithContentsOfFile: then produce instances of those private subclasses.
From the outside, the cluster nature ofNSArray is not readily apparent most of the time. It usually makes itself known if you start introspecting the classes of objects, and confuses programmers when they create anNSArray and then start getting messages about anNSCFArray. Other than that,NSArray mostly looks and acts like any other class.
There is one place where the cluster nature is hugely important, and that's if you subclass the public class yourself.
Subclassing
Subclassing a class cluster (which means subclassing an abstract class) is completely different from subclassing a normal class.
When subclassing a normal class, your superclass provides full functionality for whatever it does. A subclass with an empty implementation is completely valid in this case, and will behave just like the superclass. You can then add methods to your implementation to add new functionality or override existing functionality.
When subclassing a class cluster, your superclass doesnot provide full functionality. It provides a lot of ancillary functionality, but you must provide the core yourself. This means that an empty subclass isnot valid. There is a minimum set of methods that you must implement.
In class cluster teminology, those methods that you must implement are calledprimitive methods. How do you find them? There are two easy ways.
The first way is to crack open the documentation for the cluster class and search it for the word "primitive". The docs will tell you which methods you have to override.
The second way is to open the header for the cluster class. Primitive methods are always found in the class's main@interface block. Additional methods provided by the cluster are always found in categories.
Watch out when looking at cluster classes which are themselves subclasses of another cluster class. The result inherits all primitive methods, and you must implement both sets. For example,NSMutableArray has five primitive methods of its ownplus the two fromNSArray. If you subclassNSMutableArray, you must provide implementations for all seven.
Techniques
Now you know what to implement, but how? There are three main ways.
First, you can simply provide your own implementation of the primitive methods, implementing them all from scratch. For example, imagine you're writing a specialized array optimized for holding two elements:
@interfaceMyPairArray :NSArray{id_objs[2];}-(id)initWithFirst:(id)firstsecond:(id)second;@end@implementationMyPairArray-(id)initWithFirst:(id)firstsecond:(id)second{if((self=[selfinit])){_objs[0]=[firstretain];_objs[1]=[secondretain];}returnself;}-(void)dealloc{[_objs[0]release];[_objs[1]release];[superdealloc];}-(NSUInteger)count{return2;}-(id)objectAtIndex:(NSUInteger)index{if(index>=2)[NSExceptionraise:NSRangeExceptionformat:@"Index (%ld) out of bounds",(long)index];return_objs[index];}@end
Second, you can keep a working instance around, obtained from the public API, and pass your calls through to it:
@interfaceMySpecialArray :NSArray{NSArray*_realArray;}-(id)initWithArray:(NSArray*)array;@end@implementationMySpecialArray-(id)initWithArray:(NSArray*)array{if((self=[selfinit])){_realArray=[arraycopy];}returnself;}-(void)dealloc{[_realArrayrelease];[superdealloc];}-(NSUInteger)count{return[_realArraycount];}-(id)objectAtIndex:(NSUInteger)index{idobj=[_realArrayobjectAtIndex:index];// do some processing with objreturnobj;}// maybe implement more methods here@end
The third technique is to simply add a category to the cluster class instead of subclassing it. People often subclass simply to add new methods, and not to modify existing functionality. In Objective-C, you can add new methods in a category:
@interfaceNSArray(FirstObjectAdditions)-(id)my_firstObject;@end@implementationNSArray(FirstObjectAdditions)-(id)my_firstObject{return[selfcount]?[selfobjectAtIndex:0]:nil;}@end
firstObject method.)Conclusion
Class clusters are different from normal classes, but are easy to subclass once you understand the differences and what they mean. You're required to implement the class cluster'sprimitive methods, which you can do by providing a from-scratch implementation, or by passing through to another instance. Finally, if your only purpose in subclassing is to add new methods, create a category instead.
That's it for this week. Come back in seven days for another crunchy post. Until then, keep your ideas coming. Friday Q&A is driven by reader ideas, so if you have a topic that you would like to see covered here,send it in!
if ((self = [self init])) { ... }[super init]?init, so I don't needsuper. In this particular case, both are equivalent, because I don't overrideinit, but if I did, I would want it to be called.
- (void)somePrimitiveMethod
{
NSLog(@"-[AbstractClass somePrimitiveMethod] called, this should never happen. Did you forget to implement -[%@ somePrimitiveMethod]?", [self class]);
[self doesNotUnderstand: _cmd];
}
isKindOfClass: checks.+alloc to refuse to allocate instances of an abstract class, but there’s little advantage to having a runtime failure in+alloc rather than slightly later when you try to actually use the object (or in the initializer).[[NSArray alloc] initWithSomething]. In this case, a subclass (or proxy) could validly be returned by+alloc,-initWithSomething, or both, and this could change between system versions.NSMutableString does? You could allow that with extra keywords and such, but it adds complication.
+ (id)allocWithZone: (NSZone *)zone
{
if(self == [MyAbstractClass class])
return [MyAbstractClassInitFactory allocWithZone: zone];
else
return [super allocWithZone: zone];
}
@implementation MyAbstractClassInitFactory
- (id)init
{
[self release];
return [[MyConcreteClassThatHandlesInit alloc] init];
}
- (id)initWithSomeObject: (id)
{
[self release];
return [[MyConcreteClassThatHandlesSomeObject alloc] init];
}
// etc.
Add your thoughts, post a comment:
Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.