2

What would be a nice pattern in Objective-C for class variables that can be "overridden" by subclasses?

Regular Class variables are usually simulated in Objective-C using a file-local static variables together with exposed accessors defined as Class methods.

However, this, as any Class variables, means the value is shared between the class and all its subclasses. Sometimes, it's interesting for the subclass to change the value for itself only. This is typically the case when Class variables are used for configuration.

Here is an example: in some iOS App, I have many objects of a given common abstract superclass (Annotation) that come in a number of concrete variations (subclasses). All annotations are represented graphically with a label, and the label color must reflect the specific kind (subclass) of its annotation. So all Foo annotations must have a green label, and all Bar annotations must have a blue label. Storing the label color in each instance would be wasteful (and in reality, perhaps impossible as I have many objects, and actual configuration data - common to each instance - is far larger than a single color).

At runtime, the user could decide that all Foo annotations now will have a red label. And so on.

Since in Objective-C, Classes are actual objects, this calls for storing the Foo label color in the Foo class object. But is that even possible? What would be a good pattern for this kind of things? Of course, it's possible to define some sort of global dictionary mapping the class to its configuration value, but that would be kind of ugly.

Jean-Denis Muys
  • 6,772
  • 7
  • 45
  • 71
  • Why not simply override the class method in subclasses? – omz Aug 23 '11 at 13:09
  • And return another constant? How then can the user change the Foo label color? In that case return the value of another global? So for each subclass you define as many new globals as there are configuration variables? Yeah it would work I guess. But ugly however. – Jean-Denis Muys Aug 23 '11 at 13:22
  • Obj-C doesn't have real class variables. Have a look at the [Variables and Class Objects chapter](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocObjectsClasses.html#//apple_ref/doc/uid/TP30001163-CH11-87193) in the Objective-C Programming Language Guide for some patterns that can be used. – omz Aug 23 '11 at 13:46
  • I know about that document. As I wrote, regular class variables, though lacking in the language, are easily simulated using globals. But that's not what I am after here. I am looking for pattern to simulate class instance variables, which the document you quote doesn't even mention nor acknowledge in any way (AFAICS). – Jean-Denis Muys Aug 23 '11 at 13:55
  • 1
    I'm not sure if they work for class objects, but perhaps [associative references](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocAssociativeReferences.html#//apple_ref/doc/uid/TP30001163-CH24-SW1) could be a way to simulate those. – omz Aug 23 '11 at 14:44
  • Yeah, or have different methods returning different labels. And let the configuration swizzle them to return the right label. But really.. as Objective-C does not support class variables, maybe you should just look for a different design instead of trying to macguyver that somehow on it at runtime. – w-m Aug 23 '11 at 16:18

2 Answers2

1

Of course, it's possible to define some sort of global dictionary mapping the class to its configuration value, but that would be kind of ugly.

Why do you think this would be ugly? It is a very simple approach since you can use [self className] as the key in the dictionary. It is also easy to make it persistent since you can simply store the dictionary in NSUserDefaults (as long as it contains only property-list objects). You could also have each class default to its superclass's values by calling the superclass method until you find a class with a value.

+ (id)classConfigurationForKey:(NSString *)key {
    if(_configurationDict == nil) [self loadConfigurations]; // Gets stored values
    Class c = [self class];
    id value = nil;
    while(value == nil) {
        NSDictionary *classConfig = [_configurationDict objectForKey:[c className]];
        if(classConfig) {
            value = [classConfig objectForKey:key];
        }
        c = [c superclass];
    }
    return value;
}
+ (void)setClassConfiguration:(id)value forKey:(NSString *)key {
    if(_configurationDict == nil) [self loadConfigurations]; // Gets stored values
    NSMutableDictionary *classConfig = [_configurationDict objectForKey:[self className]];
    if(classConfig == nil) {
        classConfig = [NSMutableDictionary dictionary];
        [_configurationDict setObject:classConfig forKey:[self className]];
    }
    [classConfig setObject:value forKey:key];
}

This implementation provides no checking to make sure you don't go over the top superclass, so you will need to ensure that there is a value for that class to avoid an infinite loop.

If you want to store objects which can't be stored in a property list, you can use a method to convert back and forth when you access the dictionary. Here is an example for accessing the labelColor property, which is a UIColor object.

+ (UIColor *)classLabelColor {
    NSData *data = [self classConfigurationForKey:@"labelColor"];
    return [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
+ (void)setClassLabelColor:(UIColor *)color {
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:color];
    [self setClassConfiguration:data forKey:@"labelColor"];
}
ughoavgfhw
  • 39,734
  • 6
  • 101
  • 123
  • You focus too much on the sample use case rather than the core question (class instance variables), but your solution addresses it with minor modifications. Renaming your methods, a use would be `MyType aValue = [[self class] instanceVarForKey:@"varName"];` which would not be so bad. I still find it ugly because it requires *two* dictionary lookups. Also of course, your solution only can be used for Object types, cannot differentiate between nonexistent class instance variables and nil values, and has no type checking whatsoever (which last point I'm afraid might be inevitable). – Jean-Denis Muys Aug 24 '11 at 06:44
0

my answer here may help:

What is the recommended method of styling an iOS app?

in that case, your annotation just holds a reference to a style (e.g. you need only one per style), and the size of a pointer for an entire style is not bad. either way, that post may give you some ideas.

Update

Jean-Denis Muys: That addresses the sample use case of my question, but not my question itself (a pattern to simulate class instance variables).

you're right, i didn't know how closely your example modeled your problem and i considered commenting on that.

for a more general and reusable solution, i'd probably just write a threadsafe global dictionary if your global data is nontrivial (as you mentioned in your OP). you could either populate it in +initialize or lazily by introducing a class method. then you could add a few categories to NSObject to access and mutate the static data -- do this for syntactical ease.

i suppose the good thing about that approach is that you can reuse it in any program (even though it may appear ugly or complex to write). if that's too much locking, then you may want to divide dictionaries by prefixes or create a simple thread safe dictionary which your class holds a reference to -- you can then synthesize an instance variable via the objc runtime to store it and declare an instance method to access it. the class method would still have to use the global data interface directly.

Community
  • 1
  • 1
justin
  • 104,054
  • 14
  • 179
  • 226
  • Yep interesting. That addresses the sample use case of my question, but not my question itself (a pattern to simulate class instance variables). – Jean-Denis Muys Aug 24 '11 at 06:38