Cocoa:Categories and Informal Protocols with virtual Instance Variables
One thing that I get a kick out of is how the so-called "expert" Cocoa programmers repeat over and over, ad naseum how "Categories" are cool, as long as you understand that you can add methods to existing base classes, but not instance variables.
While this may be true in a strict "syntactical sense", it is certainly not true in the semantic sense. Let's review some principles
Objective-C categories have as their distant cousins, C++ multiple inheritance, and COM (component object model) aggregation. So if you're familiar with either of those, you will understand them easily.
First of all, an informal protocol is essentially an implementation free declaration of methods around an Objective-C classname.
/* NSObject_InformalProtocol.h */
Categories allow classes which are organized in different places of the class hierarchy to implement groups of methods, or interfaces, which allow behavior to be systematically organized irrespective of position in the inheritance taxonomy.
The problem comes when we want to implement a protocol across some disparate range of objects in the class hierarchy, but to effectively do so, each class which understands the protocol needs some consistent 'private data' or instance variables.
Syntactically, categories define only methods, and as such cannot extend the private data structures of existing classes. However, it is possible to include, among the category methods a method which promises to return instance storage for implementors of the protocol. This basically allows us to defer the allocation of instance variables to some later time, just as we are deferring the implementation of methods to some later time.
I have provided a small, simple example which demonstrates one way this can be done. In the code example, any class adhering to (ie. implementing) the NSWindow(DataGory) is required, by the protocol definition (and enforced by the default implementation) to allocate private storage for the DataGory instance variables. This allows us to organize the 'virtual self' in a typedef, and include it in the exposed definition of NSWindow(DataGory):
/* additional virtual 'instance vars' required by this category */
typedef struct tagNSWindow_DataGory
/* ok, here are the additional methods we are adding to NSWindow */
/* public */
/* protected */
-(NSWindow_DataGory*) get_DataGory_vself; /*implemented by derived classes */
The code example demonstrates adding NSWindow(DataGory) to both a class derived from NSWindow, and another class derived from NSPanel (which inherits from NSWindow). The example itself does nothing. The aim is demonstrate the organizational structure of the technique.
You protest. You say new categories of NSWindow are not useful unless they can be used without requiring the existing NSWindow 'know' anything about the new category. Under these circumstances, a slightly alternate strategy can be used. At least with NSWindow objects.
Basically, to make NSWindow(DataGory) applicable to all instances of classes derived from NSWindow, we alter the method:-(NSWindow_DataGory*) get_DataGory_vself
Currently, in the code sample, this stub simply performs an NSAssert that always fails and returns a non-crashing set of instance variables. To add NSWindow(DataGory) to all NSWindow instances, we would need some additional static storage, something like a C++ map or a CFMutableDictionary. I'm using map and thus declare:
class class_NSWindow_DataGory : public NSWindow_DataGory
/* initialize members to default values */
#include <map> /* stdc++ template library */
typedef id void_object_id_t;
typedef map<void_object_id_t, class_NSWindow_DataGory > map_objectid_to_DataGory_t;
static map_objectid_to_DataGory_t sMapObjectToDataGory;
NSWindow_DataGory* virtual_self = &sMapObjectToDataGory[self]; /* creates new storage if necessary */
/* register notification of NSWindow deletion/release here */
/* ie. something like
where -(void)dataGoryDeleteMe cleans up the mapped storage for itself
If you're going to be using categories to add new functionality to classes like NSWindow, I'm warning you now, you better prefix your method and category names with some serious disambiguation text.