Raw​Option​Set​Type

Written by Mattt Thompson

In Objective-C, NS_ENUM & NS_OPTIONS are used to annotate C enums in such a way that sets clear expectations for both the compiler and developer. Since being introduced to Objective-C with Xcode 4.5, these macros have become a standard convention in system frameworks, and a best practice within the community.

In Swift, enumerations are codified as a first-class language construct as fundamental as a struct or class, and include a number of features that make them even more expressive, like raw types and associated values. They’re so perfectly-suited to encapsulating closed sets of fixed values, that developers would do well to actively seek out opportunities to use them.

When interacting with frameworks like Foundation in Swift, all of those NS_ENUM declarations are automatically converted into an enum—often improving on the original Objective-C declaration by eliminating naming redundancies:

enum UITableViewCellStyle : Int {
    case Default
    case Value1
    case Value2
    case Subtitle
}
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
   UITableViewCellStyleDefault,
   UITableViewCellStyleValue1,
   UITableViewCellStyleValue2,
   UITableViewCellStyleSubtitle
};

Unfortunately, for NS_OPTIONS, the Swift equivalent is arguably worse:

struct UIViewAutoresizing : RawOptionSetType {
    init(_ value: UInt)
    var value: UInt
    static var None: UIViewAutoresizing { get }
    static var FlexibleLeftMargin: UIViewAutoresizing { get }
    static var FlexibleWidth: UIViewAutoresizing { get }
    static var FlexibleRightMargin: UIViewAutoresizing { get }
    static var FlexibleTopMargin: UIViewAutoresizing { get }
    static var FlexibleHeight: UIViewAutoresizing { get }
    static var FlexibleBottomMargin: UIViewAutoresizing { get }
}
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
   UIViewAutoresizingNone                 = 0,
   UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
   UIViewAutoresizingFlexibleWidth        = 1 << 1,
   UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
   UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
   UIViewAutoresizingFlexibleHeight       = 1 << 4,
   UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

RawOptionsSetType is the Swift equivalent of NS_OPTIONS (or at least as close as it gets). It is a protocol that adopts the RawRepresentable, Equatable, BitwiseOperationsType, and NilLiteralConvertible protocols. An option type can be represented by a struct conforming to RawOptionsSetType.

Why does this suck so much? Well, the same integer bitmasking tricks in C don’t work for enumerated types in Swift. An enum represents a type with a closed set of valid options, without a built-in mechanism for representing a conjunction of options for that type. An enum could, ostensibly, define a case for all possible combinations of values, but for n > 3, the combinatorics make this approach untenable. There are a few different ways NS_OPTIONS could be implemented in Swift, but RawOptionSetType is probably the least bad.

Compared to the syntactically concise enum declaration, RawOptionsSetType is awkward and cumbersome, requiring over a dozen lines of boilerplate for computed properties:

struct Toppings : RawOptionSetType, BooleanType {
    private var value: UInt = 0

    init(_ value: UInt) {
        self.value = value
    }

    // MARK: RawOptionSetType

    static func fromMask(raw: UInt) -> Toppings {
        return self(raw)
    }

    // MARK: RawRepresentable

    static func fromRaw(raw: UInt) -> Toppings? {
        return self(raw)
    }

    func toRaw() -> UInt {
        return value
    }

    // MARK: BooleanType

    var boolValue: Bool {
        return value != 0
    }


    // MARK: BitwiseOperationsType

    static var allZeros: Toppings {
        return self(0)
    }

    // MARK: NilLiteralConvertible

    static func convertFromNilLiteral() -> Toppings {
        return self(0)
    }

    // MARK: -

    static var None: Toppings           { return self(0b0000) }
    static var ExtraCheese: Toppings    { return self(0b0001) }
    static var Pepperoni: Toppings      { return self(0b0010) }
    static var GreenPepper: Toppings    { return self(0b0100) }
    static var Pineapple: Toppings      { return self(0b1000) }
}

As of Xcode 6 Beta 6, RawOptionSetType no longer conforms to BooleanType, which is required for performing bitwise checks.

One nice thing about doing this in Swift is its built-in binary integer literal notation, which allows the bitmask to be computed visually. And once the options type is declared, the usage syntax is not too bad.

Taken into a larger example for context:

struct Pizza {
    enum Style {
        case Neopolitan, Sicilian, NewHaven, DeepDish
    }

    struct Toppings : RawOptionSetType { ... }

    let diameter: Int
    let style: Style
    let toppings: Toppings

    init(inchesInDiameter diameter: Int, style: Style, toppings: Toppings = .None) {
        self.diameter = diameter
        self.style = style
        self.toppings = toppings
    }
}

let dinner = Pizza(inchesInDiameter: 12, style: .Neopolitan, toppings: .Pepperoni | .GreenPepper)

A value membership check can be performed with the & operator, just like with unsigned integers in C:

extension Pizza {
    var isVegetarian: Bool {
        return toppings & Toppings.Pepperoni ? false : true
    }
}

dinner.isVegetarian // false

In all fairness, it may be too early to really appreciate what role option types will have in the new language. It could very well be that Swift’s other constructs, like tuples or pattern matching—or indeed, even enums—make options little more than a vestige of the past.

Either way, if you’re looking to implement an NS_OPTIONS equivalent in your code base, here’s an Xcode snippet-friendly example of how to go about it:

struct <# Options #> : RawOptionSetType, BooleanType {
    let rawValue: UInt
    init(nilLiteral: ()) { self.value = 0 }
    init(_ value: UInt = 0) { self.value = value }
    init(rawValue value: UInt) { self.value = value }
    var boolValue: Bool { return value != 0 }
    var rawValue: UInt { return value }
    static var allZeros: <# Options #> { return self(0) }

    static var None: <# Options #>         { return self(0b0000) }
    static var <# Option #>: <# Options #>     { return self(0b0001) }
    // ...
}
NSMutableHipster

Questions? Corrections? Issues and pull requests are always welcome — NSHipster is made better by readers like you.

This article uses Swift version 1.2. Find status information for all articles on the status page.

Follow NSHipster
Recommended Reading
NSHipster: Obscure Topics in Cocoa & Swift
29

NSHipster, Second Edition

Obscure Topics in Cocoa & Swift

Revised and extended to focus on using Swift in iOS and OS X development, NSHipster: Obscure Topics in Cocoa & Swift is an essential updated guide.

Buy Now on Gumroad
Next Article

Since time immemorial, iOS developers have been perplexed by a singular question: ‘How do you resize an image?’. This article endeavors to provide a clear answer to this eternal question.