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

Building Swift-Style Enums in ObjC, Part 2

In the previous article, I took a look at building “traditional” enums from Swift into ObjC. This time, we’ll take a look at how we might build the “associative value” enum.

Feature Set

Swift also allows you to create “enum” values that are able to store various pieces of data stored alongside that enum value. The requirements are essentially this:

  1. Store associated values of any given type
  2. The value types can be different for each member of the enumeration

Swift Examples

In Swift, one such enum might look like this:

enum Barcode {
    case UPCA(Int, Int, Int, Int)
    case QRCode(String)
}

swift

You create one like this:

var productBarcode = Barcode.UPCA(8, 85909, 51226, 3)

And you use it like this:

switch productBarcode {
case .UPCA(let numberSystem, let manufacturer, let product, let check):
    println("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).")
case .QRCode(let productCode):
    println("QR code: \(productCode).")
}

swift

Essentially what you are creating is a set of classes that hold data grouped under a Barcode namespace. We can do something similar in ObjC.

ObjC Usage

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

Barcode *productBarcode = [Barcode UPCA:8 :85909 :51226 :3];

Pretty similar to Swift, not too bad.

And how about that switch-statement? Well, we can do it with an if-statement instead:

if ([productBarcode isKindOfClass:[UPCA class]]) {
    NSLog(@"UPC-A: %ld, %ld, %ld, %ld", [(UPCA *)productBarcode part1],
                                        [(UPCA *)productBarcode part2],
                                        [(UPCA *)productBarcode part3],
                                        [(UPCA *)productBarcode part4]);
}
else {
    NSLog(@"QR code: %@", [(QRCode *)productBarcode part1]);
}

objc

Ok, the ObjC version is starting to look ugly again because of a syntax problem; we cannot pull out the components and nicely as we can in Swift… but we’ll address that later too.

ObjC Implementation

Ok… so how do we implement this in ObjC? Well, remember, this is really just a special type that is getting returned depending on which “enum” value you want.

@class UPCA;
@class QRCode;

@interface Barcode : NSObject

+ (UPCA *)UPCA:(NSInteger)part1 :(NSInteger)part2 :(NSInteger)part3 :(NSInteger)part4;
+ (QRCode *)QRCode:(NSString*)barcode;

@end

@interface UPCA : Barcode
@property (assign) NSInteger part1;
@property (assign) NSInteger part2;
@property (assign) NSInteger part3;
@property (assign) NSInteger part4;
@end

@interface QRCode : Barcode
@property (copy) NSString *part1;
@end

@interface UPCA (Private)
- (instancetype)initWith:(NSInteger)part1 :(NSInteger)part2 :(NSInteger)part3 :(NSInteger)part4;
@end

@interface QRCode (Private)
- (instancetype)initWith:(NSString *)part1;
@end

@implementation Barcode

+ (UPCA *)UPCA:(NSInteger)part1 :(NSInteger)part2 :(NSInteger)part3 :(NSInteger)part4
{
    return [[UPCA alloc] initWith:part1 :part2 :part3 :part4];
}

+ (QRCode *)QRCode:(NSString*)barcode
{
    return [[QRCode alloc] initWith:barcode];
}

@end

@implementation UPCA

- (instancetype)initWith:(NSInteger)part1 :(NSInteger)part2 :(NSInteger)part3 :(NSInteger)part4
{
    if (self = [super init]) {
        self.part1 = part1;
        self.part2 = part2;
        self.part3 = part3;
        self.part4 = part4;
    }

    return self;
}

@end

@implementation QRCode

- (instancetype)initWith:(NSString *)part1
{
    if (self = [super init]) {
        self.part1 = part1;
    }

    return self;
}

@end

objc

WOW! That is a ton of boiler-plate code. But we are there, we have the same functionality that we have in Swift. Would you I want to write this? Eh… no. But, the point here is just to see how we might implement these features from Swift in ObjC directly. There are of course many optimizations we can do and generalizations to make; this is only one naïve implementation of “associated values”.

More next time.

Building Swift-Style Enums in ObjC, Part 2

Building Swift-Style Enums in ObjC, Part 1

I thought it would be an interesting thought experiment if we take a look at features that are present in Swift and see what it would take to build them out in ObjC.

Up on the docket today is the Enum. Of course, Swift really supports three different types of enums:

  1. The "traditional" enum — simply a list of potential options
  2. An "associated value" enum — enums with particular values that can be held but grouped under a context
  3. The "raw value" enum — an enum that stores a specific value for each of the options.

In this post, we'll be looking at the "traditional" enum.

Feature Set

So the traditional enum is quite basic and needs only to implement the following rules:

  1. Each value represents a unique option
  2. There is no intrinsic value to any given option

Optionally, it would be nice to be able to:

  1. Iterate over all of the options
  2. Know the number of options available

Implementation There are a couple of options available to us:

  1. Use the C-style enum that is already supported in ObjC
  2. Build the enum using @interface (e.g. classes)

I'll be going with option #2 as it provides the most flexibility and allows us to be true to all of the rules.

I'll be creating the CompassPoint enum from the Swift language guide.

enum CompassPoint {
    case North
    case South
    case East
    case West
}

swift

In ObjC, we need to have the following:

@interface CompassPoint : NSObject

+ (CompassPoint *)North;
+ (CompassPoint *)South;
+ (CompassPoint *)East;
+ (CompassPoint *)West;

@end

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

@implementation CompassPoint

+ (CompassPoint *)North { RETURN_ENUM_INSTANCE(); }
+ (CompassPoint *)South { RETURN_ENUM_INSTANCE(); }
+ (CompassPoint *)East  { RETURN_ENUM_INSTANCE(); }
+ (CompassPoint *)West  { RETURN_ENUM_INSTANCE(); }

@end

objc

Well… that's looking a bit ugly, but we've met the initial requirements.

Usage of the enum is almost identical to Swift, with two caveats:

  1. Unable to use the shorter dot-notation
  2. Unable to use the switch-statement

Here's what the usage code looks like:

CompassPoint *directionToHead = CompassPoint.South;
if (directionToHead == CompassPoint.North) {
    NSLog(@"Lots of planets have a north");
}
else if (directionToHead == CompassPoint.South) {
    NSLog(@"Watch out for penguins");
}
else if (directionToHead == CompassPoint.East) {
    NSLog(@"Where the sun rises");
}
else if (directionToHead == CompassPoint.West) {
    NSLog(@"Where the skies are blue");
}

objc

The nice thing about this ObjC version is that it's quite easy to add some additional functionality, like the ability to iterate over all of the values:

@interface CompassPoint (Iteration)
+ (NSArray *)allValues;
@end

@implementation CompassPoint (Iteration)
+ (NSArray *)allValues
{
    static NSArray *values = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        values = @[ CompassPoint.North, CompassPoint.South, CompassPoint.East, CompassPoint.West ];
    });

    return values;
}

@end

Obviously the syntax for the ObjC sucks, especially in comparison to what we get in Swift. There are lots of different options available for us to make this better in ObjC, but more on that later.

Building Swift-Style Enums in ObjC, Part 1