Reader beware: this post is part of the older "Objective-C era" on Cocoa with Love. I don't keep these articles up-to-date and many contain code that no longer works or is superceded by newer APIs. Many others contain out-of-date information or offer advice and opinions I no longer endorse. Read "A new era for Cocoa with Love" for more.
The default Cocoa collection classes are highly capable but there are situations where you may need to override them to alter their functionality. I'll explain when and how you should do this with an example class: OrderedDictionary (an NSMutableDictionary subclass that maintains ordering of its keys).
Best practice: don't subclass
This post is going to discuss subclassing
NSMutableDictionary. You should note that this is a special case; most of the time when you add functionality to a collection class, you should not override it.
Most classes are not designed with subclassing in mind. If you are not the author of a class, choosing to override it incurs a risk that your additions won't maintain implicit or private values and behaviors of the object correctly.
Increased risk due to subclassing does not apply to classes that are intended to be subclassed before use. Prominent examples include
NSCell. With classes designed for use through subclasses, any risk associated with the subclassing itself is more than outweighed by the well-tested base functionality that they provide, which cannot be accessed unless the class is subclassed.
Aside: design patterns that customize behavior without subclassing
- Put the custom functionality in the parent which contains the non-overridden object, rather than in the non-overridden object itself. This is "has-a" design instead of "is-a".
- Use a Decorator object instead. A Decorator is a wrapper around the non-overridden class. All messages to the contained class go through the Decorator first, so the Decorator can control or supplement the behavior of the contained class.
- Use an Observer to keep the non-overridden object synchronized with dependent objects so that their combined state achieves custom behavior, even though each of the objects remains non-custom. In this case, the Observer acts as a Controller/Manager to the dependent objects.
Choosing to override a collection class
The collection classes in Cocoa are class clusters. In some respects, this means that they are intended to be overridden, since the base class is functionally abstract and contains none of the required concrete functionality.
Default concrete functionality for a class cluster is provided by hidden subclasses, returned transparently by the base class. Since this is transparent, you are not normally expected to write the subclasses yourself. Despite this expectation, the interface has been designed with subclassing in mind, so we can easily implement the required subclass behavior ourselves instead of relying upon one of the default implementations.
The functionality which constitutes "concrete" behavior for a class cluster is given in Apple's documentation as the "primitive" methods of the cluster. For collection classes (like
NSMutableDictionary), these "primitive" methods provide all behavior for the collection's storage. Since we inherit no default behavior for these primitive methods, this means that the decision to make subclass of a collection class in Cocoa is a decision to reimplement the storage.
Design of an Ordered NSMutableDictionary subclass
I have chosen to implement an ordered version of
OrderedDictionary. This requires ordered storage of dictionary keys but
NSDictionary stores its keys in a hash table, which is unordered by design. Since this lack of order is fundamental to the hash table storeage, our subclassing of
NSMutableDictionary (and hence reimplementation of the storage) is appropriate.
However, I will reimplement basic storage for my subclass by storing all objects and keys in an unmodified
NSMutableDictionary. From a design point-of-view, this will make my subclass a Decorator around the unmodified
NSMutableDictionary, rather than a pure subclass.
As messages are passed from the "primitive" methods on my subclass through to the contained
NSMutableDictionary, I will also store the dictionary's keys in a separate
NSMutableArray, thereby allowing the
OrderedDictionary to keep the ordering for the keys, in addition to standard
Implementing the primitive methods
Apple's documentation lists the following "primitive" methods for
In reality, the following method is also required, despite not being listed:
In accordance with the Decorator pattern that I've chosen, almost all of these are implemented by passing the message through to the contained
NSMutableDictionary (named "
dictionary"), with keys also added to the
NSMutableArray (named "
array") and the
keyEnumerator method returning the
objectEnumerator of "
You can download the complete OrderedDictionary class here (2kb). I've further added
descriptionWithLocale:indent: methods to the downloadable version to fill out some non-essential functionality.
Creating a subclass of
NSMutableDictionary is made relatively simple by the clean set of "primitive" methods for the class cluster. With these implemented, the remaining rich functionality of
NSMutableDictionary become available; no further work required.
My point about the risks of subclassing should become apparent with the undocumented requirement of
initWithCapacity:. The necessity of this method is not mentioned in the documentation, so I was forced to use feedback from debugging information to rework the design of the class to reach a functional implementation — trivial in this case but annoying nonetheless.
The inclusion of the
descriptionWithLocale:indent: method in the class is another lesson along the same lines: the default implementation of this method assumes it can reorder the keys returned from
keyEnumerator without causing problems. After seeing erroneous results during testing, I was required to replace this method to remove the incorrect assumption.