Once I started using my enumerations class, I quickly ran into a problem: how do I get all the enumeration values? It sounds like a simple problem to fix, but it turned out to be a bit more complicated.

Getting All Values

The immediate obvious idea is to keep a static array of all the values. But there’s a problem in how this array gets populated. It can’t be done in the BRUnits initialize class method. Due to the way the macros work BRUnits actually has no knowledge of what all it’s values are.

Another thought was to add the values in the initialize method of each of the value classes. This is no good since there’s no guarantee that the subclasses will be loaded when [BRUnits allValues] is called.

Yet another failed idea was to simply look for all the subclasses of BRUnits and figure out the methods for the values from there. However, I couldn’t find any way to easily determine what a classes subclasses are (I assume because, due to the dynamic nature of Objective-C, this can change). It is possible, but you’d need to sift through all the classes to due so (feel free to correct me if I’ve overlooked something).

So I basically ended up doing something to what I did for finding test methods in a test class. I go through all the class methods and pick ones that look like they might be value methods, call them and add the results to an array. Luckily, since the return value is just an NSArray, we can stick the implementation in BREnumType and not in a macro.

+(NSArray*)allValues
{
    NSMutableArray* enumValues = [NSMutableArray array];
    unsigned int methodCount;
    Method* methods = class_copyMethodList(object_getClass(self), &methodCount);
    for (int i = 0; i < methodCount; ++i) {
        SEL methodSelector = method_getName(methods[i]);
        NSString* methodName = NSStringFromSelector(methodSelector);
        if ((strcmp(EnumValueMethodSignature, method_getTypeEncoding(methods[i])) == 0) &&
            (![methodName isEqualToString:@"allValues"])) {
            id result = [self performSelector:methodSelector];
            if ([result isKindOfClass:[BREnumType class]]) {
                [enumValues addObject:result];
            }
        }
    }
    free(methods);
    return enumValues;
}

Although I’m fairly defensive about it, it’s still open to some problems. If there are other class methods that match the signature (take no parameters and return an object), they are going to get called. If the result isn’t an instance BREnumType it gets discarded, but that doesn’t change the fact that the method gets called. For example, if you did the following:

@interface BRUnits(DefaultValue)
+(BRUnits*)defaultValue;
@end

@implementation BRUnits(DefaultValue)
+(BRUnits*)defaultValue
{
    return [self centimeters];
}
@end

Well, that’s a problem. It’s not going to get caught by the filter and centimeters is going to be in the list twice. There are a couple of solutions to this: I could check before adding the value to see if it’s already in the enumValues array in this case. A more general solution might be to offer a way of filtering values by implementing a method. I will be lazy and leave it the way it is for now. I’m still not really satisfied with this implementation, but I wasn’t able to come up with something I’m happier with. It’s sufficient for now, but if someone smarter than me has better ideas, I’m happy to hear them.

Thread Safety

I mentioned last post that the value methods weren’t thread-safe and since I was changing this stuff, I figured I’d fix them. They can be really easily fixed by just putting a lock around the body of the method. For the sake of clarity, in my examples, I’m pretending I haven’t changed all the code to macros.

+(BRUnits*)centimeters {
    @synchronized(self) {
        if (_BRUnits_centimeters == nil) {
            _BRUnits_centimeters = [[[BRUnits_centimeters class] alloc] initWithName:@"centimeters"];
        }
    }
    return _BRUnits_centimeters;
}

This is completely safe, but we’re paying the penalty of a lock every time, when it’s really only needed the first time we create the instance. So we’ll use double checked locking. The concept is simple:

+(BRUnits*)centimeters {
    if (_BRUnits_centimeters == nil) {
        @synchronized(self) {
            if (_BRUnits_centimeters == nil) {
                _BRUnits_centimeters = [[[BRUnits_centimeters class] alloc] initWithName:@"centimeters"];
            }
        }
    }
    return _BRUnits_centimeters;
}

We check for nil both before and after the synchronized section is entered, since another thread might have come in between the check and the lock. However there are really two issues: one has to do with optimizations a compiler might make. It might load the value of _BRUnits_centimeters into a register and not reload it after the lock. In this case, another thread may have changed the in-memory value and our thread wouldn’t know about it. The other problem is that on machines with multiple cores, writes to memory from one core might occur later, meaning that even if we were to ensure that we load the value from memory, it still might not be correct. I’m somewhat simplifying the issues, but they’re discussed in great detail here: C++ and the Perils of Double-Checked Locking, and with from an Objective-C / MacOS X point of view here: Locking, double-checked locking and speed.

The second article helps us a lot with this issue by giving us specific implementations, however there’s one point I’d disagree on. The article would have us implement the method like this (note: _BRUnits_centimeters is now being declared as a volatile pointer):

+(BRUnits*)centimeters {
    OSMemoryBarrier();
    if (_BRUnits_centimeters == nil) {
        @synchronized(self) {
            if (_BRUnits_centimeters == nil) {
                _BRUnits_centimeters = [[[BRUnits_centimeters class] alloc] initWithName:@"centimeters"];
                OSMemoryBarrier();
            }
        }
    }
    return _BRUnits_centimeters;
}

My understanding of OSMemoryBarrier is that it ensures all read and write operations from all cores up to that point are finished (I’ll readily admit that I’m no expert on this though). Given this fact, it seems only important that we do this before our second check, not the first. Given that we want to optimize for the most common case (when the instance has already been created), we’d eliminate all of the more expensive calls for that case and end up with:

+(BRUnits*)centimeters {
    if (_BRUnits_centimeters == nil) {
        @synchronized(self) {
            OSMemoryBarrier();
            if (_BRUnits_centimeters == nil) {
                _BRUnits_centimeters = [[[BRUnits_centimeters class] alloc] initWithName:@"centimeters"];
                OSMemoryBarrier();
            }
        }
    }
    return _BRUnits_centimeters;
}

This seems like it would be functionally equivalent to me. Unfortunately, I’ve tried getting a version without OSMemoryBarrier to break on my dual core machine, and I just couldn’t (I assume it’d be easier to make it fail on a beefier machine). So, I can’t really be 100% certain that this does would I intend, but I’m going to go ahead and pretend that I’m right for now.

So that’s it for now…at least until I start using it and find something else lacking.

EnumType.zip

One Response to “Enumerations in Objective-C part 2”

  1. Allan Bazinet Says:

    For your consideration, a possible alternate approach to your threadsafe instantiation on demand quest.

    // Optimal method, undeclared in interface. First thread through the // method below will swap the implementation of the below method with // that of this one, so we take the expensive path only once.

    • (BRUnits *)centimetersInstance { return _BRUnits_centimeters; }

    // Clients will always call this method. During execution by the first // thread to land here, swap in the implementation of the method above. // Normally one would handle this instantiation in an initialize method, // but when a category like this one has an initialize, any initialize // method present in the categorized class won’t get run, and we also // have no access to it. That is, in general, really weird and thus // to be avoided.

    • (BRUnits *)centimeters { @synchronized(self) { if (_BRUnits_centimeters == nil) { _BRUnits_centimeters = [[[BRUnits_centimeters class] alloc] initWithName:@”centimeters”];

          OSMemoryBarrier();
      
          Method m1 = class_getClassMethod([BRUnits class],
                                           @selector(centimeters));
          Method m2 = class_getClassMethod([BRUnits class],
                                           @selector(centimetersInstance));
      
          method_setImplementation(m1, method_getImplementation(m2));
      }
      

      }

      return _BRUnits_centimeters; }

Leave a Reply