Implementing enum with associated values in Objective-C

Swift has this amazing thing called enum with associated value. Enums are traditionally used to represent a type system with finite set of values. But in real life each of those enum types sometimes have a need to associate some data. And the data of one enum type might be very different than the rest. Imagine writing a parser where each node is of type Node . We usually want to deal with Node as the type for doing things like passing Node around, or generate an array of Node . We might also have methods on Node , such as node.print() but Node can never be instantiated directly as n = Node() .

Swift programming guide provides a good example of this pattern with Barcode that I’ve enhanced here a bit for readability:

// Barcode.swift enum Barcode { case upc ( numberSystem : Int , manufacturer : Int , product : Int , check : Int ) case qrCode ( productCode : String ) func print () { switch self { case let . upc ( numberSystem , manufacturer , product , check ): Swift . print ( "UPC: \( numberSystem ) , \( manufacturer ) , \( product ) , \( check ) ." ) case let . qrCode ( productCode ): Swift . print ( "QR code: \( productCode ) ." ) } } }

// Main.swift func print ( barcode : Barcode ) { barcode . print () } func printBarcodes () { print ( barcode : . upc ( numberSystem : 10 , manufacturer : 20 , product : 30 , check : 40 )) print ( barcode : . qrCode ( productCode : "asdfa" )) }

How can we bring this pattern to Objective-C? This is how I think of the problem. Every use case of an enum with associated value can be represented as a subclass pattern, where every subclass is only a level deep. And the base class is an abstract class that can not be instantiated. The Barcode example from above might look something like:

With that in mind we can design our interface as:

// PLBarcode.h @protocol PLBarcode < NSObject > - ( void ) print ; @end @interface PLBarcodeUPC : NSObject < PLBarcode > + ( instancetype ) barcodeWithNumberSystem :( NSInteger ) numberSystem manufacturer :( NSInteger ) manufacturer product :( NSInteger ) product check :( NSInteger ) check ; @end @interface PLBarcodeQR : NSObject < PLBarcode > + ( instancetype ) barcodeWithProductCode :( NSString * ) productCode ; @end

Notice that we are not actually subclassing any implementation class from any common base PLBarcode class. This keeps every implementation class completely independent of each other with no shared hierarchy. We can use PLBarcode as the type that can easily pass around:

// Main.m - ( void ) printBarcode :( id < PLBarcode > ) barcode { [ barcode print ]; } - ( void ) printBarcodes { [ self printBarcode :[ PLBarcodeUPC barcodeWithNumberSystem : 10 manufacturer: 20 product: 30 check: 40 ]]; [ self printBarcode :[ PLBarcodeQR barcodeWithProductCode : @"asfsdf" ]]; }

Another advantage of enums is that we don’t have to deal with so many explicit types as we have here with PLBarcodeUPC and PLBarcodeQR . I can imagine adding more implementation types would require bombarding the clients of PLBarcode with many implementation types that they don’t actually care about. Thinking again, what we need is the following:

A way to instantiate PLBarcode with unrelated data. Ability to send messages to PLBarcode which should be handled by the implementation type.

With that in mind, we can solve this problem by moving all the implementation subclasses internally to PLBarcode.m and expose only typeless class methods in PLBarcode.h , like a factory pattern

// PLBarcode.h @protocol PLBarcode < NSObject > - ( void ) print ; @end @interface PLBarcode : NSObject + ( id < PLBarcode > ) barcodeWithNumberSystem :( NSInteger ) numberSystem manufacturer :( NSInteger ) manufacturer product :( NSInteger ) product check :( NSInteger ) check ; + ( id < PLBarcode > ) barcodeWithProductCode :( NSString * ) productCode ; @end

// PLBarcode.m @interface __PLBarcodeUPC : NSObject < PLBarcode > + ( instancetype ) barcodeWithNumberSystem :( NSInteger ) numberSystem manufacturer :( NSInteger ) manufacturer product :( NSInteger ) product check :( NSInteger ) check ; @end @interface __PLBarcodeQR : NSObject < PLBarcode > + ( instancetype ) barcodeWithProductCode :( NSString * ) productCode ; @end @implementation PLBarcode + ( id < PLBarcode > ) barcodeWithNumberSystem :( NSInteger ) numberSystem manufacturer :( NSInteger ) manufacturer product :( NSInteger ) product check :( NSInteger ) check ; { return [ __PLBarcodeUPC barcodeWithNumberSystem : numberSystem manufacturer: manufacturer product: product check: check ]; } + ( id < PLBarcode > ) barcodeWithProductCode :( NSString * ) productCode ; { return [ __PLBarcodeQR barcodeWithProductCode : productCode ]; } @end

With that change the call site looks much cleaner:

- ( void ) exampleBarcode { [ self printBarcode :[ PLBarcode barcodeWithNumberSystem : 10 manufacturer: 20 product: 30 check: 40 ]]; [ self printBarcode :[ PLBarcode barcodeWithProductCode : @"asfsdf" ]]; }

Resources