Artisanal Objective-C Sum Types

Heath Borders
Twitch Blog
Published in
3 min readMay 14, 2018

A sum type combines many possible differently-typed values into a single value, expressed in Swift as an enum.

Swift Enum Example

In C, we can achieve the power of Swift’s enum with a combination of a C union and a C enum:

First, I’m sure you notice Swift’s enum is more concise, but it’s also safer. In C, the compiler doesn’t prevent us from either creating an example with a mismatched type or from consuming a union as a mismatched type:

Trying to mismatch our enum types in Swift won’t compile. Yay!

Unfortunately, in Objective-C, we can’t use Objective-C objects in structs or unions in ARC, so we can’t use our C sum type with Objective-C objects. However, we can build a similar construct by hand.

First, we need a class to capture all of the distinct types, and expose them through a single block-based callback interface:

We must declare forward references for our distinct types, which we’ll define later. We mark new and init as NS_UNAVAILABLE so that consumers won’t be able to instantiate our base class directly. If they use -Wobjc-designated-initializers, they won’t be able to subclass our base class either (without importing ExamplePrivate.h, which we won’t distribute to them).

Next, we’ll declare our distinct types:

They’re simply plain-old-objective-c-objects that extend our base class. Our base class and our distinct types all have switchFoo:bar:. The distinct types will simply call the respective callback block with self. The base class will have an empty implementation. We use NS_REQUIRES_SUPER on switchFoo:bar: because if we add another distinct type, we want all of our implementations of switchFoo:bar: on our distinct types to be warned (thanks to -Wobjc-missing-super-calls). If we change the base class switchFoo:bar: to switchFoo:bar:quz, but don’t update the distinct type implementations, none of our callbacks will be called. This use of NS_REQUIRES_SUPER is basically a hack to give us something close to Swift’s override keyword.

We need to declare our base class’ NS_DESIGNATED_INITIALIZER so we can implement our distinct types’ initializers. As I mentioned above, we’ll put these declarations in ExamplePrivate.h, so our consumers won’t be able to directly instantiate or subclass Example. They can’t simply be declared in a .m file because the compiler only warns about nullability completeness if we declare a method in a .h file.

Implementing the base class is pretty trivial:

Implementing the distinct types are also pretty trivial. Again, notice that we must call [super switchFoo:bar:] so we’ll get a warning if we change the base class’ -switchFoo:bar:.

Finally, we’ll declare a Switcher object, which captures a generic type at a single call site for a type-safe return value. Objective-C allows us to assign values to local variables within a block, but it’s easy to accidentally miss an assignment:

This is contrived, but if you imagine four or five distinct types, you can see how it would be easier to miss an assignment. Using a proper return value is safer.

Unfortunately, Objective-C generics only capture types at a class declaration (unlike Swift generics, which can capture a type at any call site). Thus, we need to create a separate class to capture the return types for arbitrary call sites:

We still have the same risk about missing an assignment, but it’s only in one place in our entire codebase:

Now, the compiler will give us an error if we forget to return:

Now, we finally have the tools to build our Objective-C sum type. We can act on it directly:

Or we can transform its value and be guaranteed that every distinct type was properly processed with the correct type:

And we have the same type checking support Objective-C gives all of its method calls, so we don’t have to worry about passing the wrong values when we create a distinct type:

And we don’t have to worry about unpacking the wrong values from a sum type:

I realize this is a LOT of boilerplate code. The next step is to create a generator for these types since it’s all extremely mechanical, so stay tuned!

I had to put the code in separate gists in order to break it into readable chunks. The code is available as a proper Xcode project on GitHub.

Twitch is hiring! Learn about life at Twitch and check out our open roles here.

--

--

Heath Borders
Twitch Blog

#Katie's Husband; #Ben and #Sam's Dad; iOS at @Twitch; @strangeloop_stl, @cocoaconf speaker. My tweets don't represent my employer.