July 11, 2014
C-Style "typedef enum" in Swift
Two years ago, Apple introduced two macros to standardize the way C enum
types are declared: NS_ENUM
and NS_OPTIONS
. Swift imports enumerations declared using these macros automatically as native language structures that can be used in obvious and convenient ways. In retrospect, having enumerations defined with their types in a consistent way paved their inclusion in the coming type-safe Swift.
// UITableViewCellStyle defined with NS_ENUM
let cellStyle = UITableViewCellStyle.Subtitle
// UIViewAutoresizing defined with NS_OPTIONS
let resizing: UIViewAutoresizing = .FlexibleHeight | .FlexibleWidth
if resizing & .FlexibleHeight {
// do something
}
All of Apple’s APIs are defined with the appropriate macro (and therefore get imported into Swift nicely), but what if you’re using a third-party library that is a little older, or didn’t jump on the macro train?
Swift imports these older, C-style typedef enum
declarations as well, you just need to use a little extra care when working with them. A C enum
can be used for either an enumeration or an options bitmask, but Swift imports them all the same way. Let’s look at what’s happening behind the scenes, and then how to use each in turn.
Bridged enums
Since Swift doesn’t know whether an enum
defined in C is meant to be an enumeration or a bitmask, it brings them all in using the same format. Any enum
defined in C will become a struct
in Swift with a single value
property, while the individual elements are declared as global constants of that type. The value
property will be of type UInt32
unless the type was specifically declared otherwise in C.
That is to say, this enum
defined in an Objective-C header:
typedef enum {
MesozoicPeriodTriassic,
MesozoicPeriodJurassic,
MesozoicPeriodCretaceous,
} MesozoicPeriod;
gets translated to this in Swift:
struct MesozoicPeriod {
var value: UInt32
init(_ val: UInt32) { value = val }
}
let MesozoicPeriodTriassic = MesozoicPeriod(0)
let MesozoicPeriodJurassic = MesozoicPeriod(1)
let MesozoicPeriodCretaceous = MesozoicPeriod(2)
Enumerations
Enumerations are used to hold a single choice from a set – the style of a table view cell or the method of an HTTP request. How do we use imported C enumerations in Swift?
var period = MesozoicPeriodJurassic // assign like normal...
if period == MesozoicPeriodJurassic { // ...but this doesn't work
> Error: 'MesozoicPeriod' is not convertible to '_ArrayCastKind'
The problem is that the equality operator (==
) isn’t defined for these imported enumerations. We need to compare the value
properties, whether in an if or switch statement:
var period = MesozoicPeriodJurassic
if period.value == MesozoicPeriodJurassic.value {
println("Jurrasic!") // Jurrasic!
}
Keep in mind that since this isn’t a true Swift enumeration we miss out on some compiler safety guards, like requiring switch statements to exhaust all possibilities, or keeping values within the defined bounds (you can easily set period
to MesozoicPeriod(42)
if you’d like).
Options Bitmasks
Bitmasks, on the other hand, use bit arithmetic to hold multiple values simultaneously. I have a generator for making your own Swift-native bitmasks, but if you’re using a bitmask defined in a C-based library, you’re stuck with the imported version.
Let’s look at how to use this bitmask of attributes that a people-eater might have:
typedef enum {
OneEyed = 1 << 0,
OneHorned = 1 << 1,
Flying = 1 << 2,
Purple = 1 << 3
} PeopleEaterAttributes;
The big stumbling block here is that none of the bitwise operators (&
, |
, ~
, etc.) are defined for this imported bitmask’s type. So again we need to use the value
property, not only for comparison but also for operations on the bitmask.
Initialize your bitmask either using one of the global constants or as a new struct with zero value, and then modify the value
property from there.
var purplePeopleEater = PeopleEaterAttributes(0)
purplePeopleEater.value = Purple.value | OneEyed.value | OneHorned.value | Flying.value
Then, when testing your bitmask, you need to use an explicit comparison to zero. The result of a bitwise operation is another number of the same type, which Swift won’t convert to a Boolean for you.
// this only works for NS_OPTIONS-declared bitmasks:
if purplePeopleEater & Flying {
> Error: 'PeopleEaterAttributes' is not convertible to 'Bool'
// using .value is the right idea, but still doesn't work:
if purplePeopleEater.value & Flying.value {
> Error: 'UInt32' is not convertible to 'Bool'
// this is the way for C-style "typedef enum" bitmasks
if 0 != (purplePeopleEater.value & Flying.value) {
println("Flying purple people eater")
}
You can read more on bitmasks in this great piece from Big Nerd Ranch, and about the NS_ENUM and NS_OPTIONS macros at NSHipster.