Quantcast
Viewing all articles
Browse latest Browse all 382

RawOptionSetType

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:

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 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:

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 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{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)}// ...}

Viewing all articles
Browse latest Browse all 382

Trending Articles