In Objective-C, NS_ENUM
& NS_OPTIONS
are used to annotate C enum
s 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:
enumUITableViewCellStyle:Int{caseDefaultcaseValue1caseValue2caseSubtitle}
typedefNS_ENUM(NSInteger,UITableViewCellStyle){UITableViewCellStyleDefault,UITableViewCellStyleValue1,UITableViewCellStyleValue2,UITableViewCellStyleSubtitle};
Unfortunately, for NS_OPTIONS
, the Swift equivalent is arguably worse:
structUIViewAutoresizing:RawOptionSetType{init(_value:UInt)varvalue:UIntstaticvarNone:UIViewAutoresizing{get}staticvarFlexibleLeftMargin:UIViewAutoresizing{get}staticvarFlexibleWidth:UIViewAutoresizing{get}staticvarFlexibleRightMargin:UIViewAutoresizing{get}staticvarFlexibleTopMargin:UIViewAutoresizing{get}staticvarFlexibleHeight:UIViewAutoresizing{get}staticvarFlexibleBottomMargin:UIViewAutoresizing{get}}
typedefNS_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:
structToppings:RawOptionSetType,BooleanType{privatevarvalue:UInt=0init(_value:UInt){self.value=value}// MARK: RawOptionSetTypestaticfuncfromMask(raw:UInt)->Toppings{returnself(raw)}// MARK: RawRepresentablestaticfuncfromRaw(raw:UInt)->Toppings?{returnself(raw)}functoRaw()->UInt{returnvalue}// MARK: BooleanTypevarboolValue:Bool{returnvalue!=0}// MARK: BitwiseOperationsTypestaticvarallZeros:Toppings{returnself(0)}// MARK: NilLiteralConvertiblestaticfuncconvertFromNilLiteral()->Toppings{returnself(0)}// MARK: -staticvarNone:Toppings{returnself(0b0000)}staticvarExtraCheese:Toppings{returnself(0b0001)}staticvarPepperoni:Toppings{returnself(0b0010)}staticvarGreenPepper:Toppings{returnself(0b0100)}staticvarPineapple:Toppings{returnself(0b1000)}}
As of Xcode 6 Beta 6,
RawOptionSetType
no longer conforms toBooleanType
, 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:
structPizza{enumStyle{caseNeopolitan,Sicilian,NewHaven,DeepDish}structToppings:RawOptionSetType{...}letdiameter:Intletstyle:Stylelettoppings:Toppingsinit(inchesInDiameterdiameter:Int,style:Style,toppings:Toppings=.None){self.diameter=diameterself.style=styleself.toppings=toppings}}letdinner=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:
extensionPizza{varisVegetarian:Bool{returntoppings&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 enum
s—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{privatevarvalue:UInt=0init(_value:UInt){self.value=value}varboolValue:Bool{returnvalue!=0}staticfuncfromMask(raw:UInt)-><#Options#>{returnself(raw)}staticfuncfromRaw(raw:UInt)-><#Options#>?{returnself(raw)}functoRaw()->UInt{returnvalue}staticvarallZeros:<#Options#>{returnself(0)}staticfuncconvertFromNilLiteral()-><#Options#>{returnself(0)}staticvarNone:<#Options#>{returnself(0b0000)}staticvar<#Option#>:<#Options#>{returnself(0b0001)}// ...}