Building Swift-Style Enums in ObjC, Part 3

You can find part 1 and part 2 if you need to catch up. This part will be about creating Swift’s “raw value” enum type.

Feature Set This is essentially the same as the “traditional” enum with additional piece: a specific value for the given enum value. The requirements are simple:

  1. Each value represents a unique option
  2. Each value has an intrinsic value

Swift Examples This is what the Swift code looks like:

enum ASCIIControlCharacter: Character {
    case Tab = "\t"
    case LineFeed = "\n"
    case CarriageReturn = "\r"
}

swift

You assign one like this:

var controlCharacter = ASCIIControllCharacter.Tab

And you use it like this:

switch controlCharacter {
case .Tab:
    println("this is a tab")
case .LineFeed:
    println("who doesn't love newlines?")
case .CarriageReturn:
    println("notepad needs these... don't forget!")
}

ObjC Usage Let’s start with how we use it. First, how we declare it:

ASCIIControlCharacter *controlCharacter = ASCIIControlCharacter.Tab;

Like the previous examples, the switch-statement needs to be converted to an if-statement.

ASCIIControlCharacter *controlCharacter = [ASCIIControlCharacter fromRaw:@"\t"];
if (controlCharacter == ASCIIControlCharacter.Tab) {
    NSLog(@"this is a tab");
}
else if (controlCharacter == ASCIIControlCharacter.LineFeed) {
    NSLog(@"who doesn't love newlines?");
}
else if (controlCharacter == ASCIIControlCharacter.CarriageReturn) {
    NSLog(@"notepad needs these... don't forget!");
}

objc

There is a bit less typing in the Swift version, but not much1.

ObjC Implementation Now is time for the dreaded implementation… it’s always much more painful in ObjC.

@interface ASCIIControlCharacter : NSObject

@property (copy) NSString *rawValue;

- (instancetype)initWithCharacter:(NSString *)rawValue;

+ (ASCIIControlCharacter *)Tab;
+ (ASCIIControlCharacter *)LineFeed;
+ (ASCIIControlCharacter *)CarriageReturn;

+ (ASCIIControlCharacter *)fromRaw:(NSString *)rawValue;

@end

@implementation ASCIIControlCharacter

#define RETURN_ENUM_INSTANCE(CHAR) \
    static ASCIIControlCharacter *instance = nil;\
    static dispatch_once_t onceToken;\
    dispatch_once(&onceToken, ^{\
        instance = [[ASCIIControlCharacter alloc] initWithCharacter:CHAR];\
    });\
    return instance;

+ (NSMutableDictionary *)rawLookup;
{
    static NSMutableDictionary *lookup = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        lookup = [[NSMutableDictionary alloc] init];

        [lookup setObject:ASCIIControlCharacter.Tab forKey:@"\t"];
        [lookup setObject:ASCIIControlCharacter.LineFeed forKey:@"\n"];
        [lookup setObject:ASCIIControlCharacter.CarriageReturn forKey:@"\r"];
    });

    return lookup;
}

- (instancetype)initWithCharacter:(NSString *)rawValue
{
    if (self = [super init]) {
        self.rawValue = rawValue;
    }
    return self;
}

+ (ASCIIControlCharacter *)Tab              { RETURN_ENUM_INSTANCE(@"\t"); }
+ (ASCIIControlCharacter *)LineFeed         { RETURN_ENUM_INSTANCE(@"\n"); }
+ (ASCIIControlCharacter *)CarriageReturn   { RETURN_ENUM_INSTANCE(@"\r"); }

+ (ASCIIControlCharacter *)fromRaw:(NSString *)rawValue
{
    return ASCIIControlCharacter.rawLookup[rawValue];
}

@end

So yeah… without doing anything magical, the straight ObjC version is not so awesome to author compared to the Swift version. It is more flexible, but much more verbose.

The root of the problem is not the inability for ObjC to express these types, it’s simply the ease in which ObjC can do it.

It’s about the syntax. And we’re getting there… =)

  1. If we wanted, we could even set it up so that the ObjC version could hold different types of raw values; that’s something that’s not possible in Swift today.
Building Swift-Style Enums in ObjC, Part 3