Notes from WWDC 2015: The Enumerated Delights of Swift 2.0 Option Sets
Option sets received a welcome facelift in Swift 2.0. Lightweight sets of Boolean values, option sets enable you to store and query independent flags. Swift treats these as a collection of orthogonal settable switches. Now, you just build sets using simple, readable constructs, and then add and remove and test members as needed.
You do all this using almost no low-level bitwise operation programming. Option sets are easier to use than before, and the code you work with is more readable and maintainable than ever. Read on to discover how useful these new features are in your daily development work.
Back to NS_OPTIONS
To understand OptionSetTypes, it helps to step back and look at Objective C's bit fields. In iOS 6 and OS X Mountain Lion, Apple introduced NS_OPTIONS along with its sibling NS_ENUM to replace typedef enum statements.
These macros created a consistent way to build bitflags and enumerations. They allow compilers to test for completeness in switch statements, ensuring that all cases are covered. They also specify both the type and size of option and enumeration members.
Many Objective-C constructs use NS_OPTIONS to create bit flag sets. For example, you might want to add an option about extending a view controller view's top and left edges. In Objective-C, you can or together UIRectEdgeTop and UIRectEdgeLeft values provided by the UIRectEdge option set. This option set also offers a UIRectEdgeAll option that combines all edges into a single value.
typedef NS_OPTIONS(NSUInteger, UIRectEdge) { UIRectEdgeNone = 0, UIRectEdgeTop = 1 << 0, UIRectEdgeLeft = 1 << 1, UIRectEdgeBottom = 1 << 2, UIRectEdgeRight = 1 << 3, UIRectEdgeAll = UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight } NS_ENUM_AVAILABLE_IOS(7_0);
Swift imports these NS_OPTIONS to conform to the OptionSetType protocol. Swift 2.0 enhances that protocol to provide simple easy-to-use construction and testing.
Building Enumerations
Until 2.0, you tested options with low-level bitwise math. For example, you might have written the following test to determine if the .Left flag was set in a UIRectEdge value.
if edgeOptions & .Left == .Left {...}
Now, in Swift 2.0, you replace that test with a membership function:
if edgeOptions.contains(.Left) {...}
The syntax looks and feels like you're working with standard sets. You use square brackets and apply functions like contains, union, intersection, exclusive-or, and so forth.
Here's what option membership looks like in Swift 2.0.
// Create empty option set var options : UIRectEdge = [] options.insert(.Left) options.contains(.Left) // true options.contains(.Right) // false // Create another set and union var otherOptions : UIRectEdge = [.Right, .Bottom] options.unionInPlace(otherOptions) options.contains(.Right) // true
Once created, you can assign option sets to properties. This call uses the options just built to set a view controller's edges with respect to its parent's navigation container.
UIViewController().edgesForExtendedLayout = options
Building Option Sets
Default protocol implementations are another powerful Swift 2.0 feature. These enable option sets to create fully working bitflag implementations requiring almost no work on your part.
Consider the following example. The Features struct declares OptionSetType conformance, adds a rawValue field, and then declares a simple list of static flags.
struct Features : OptionSetType { let rawValue : Int static let AlarmSystem = Features(rawValue: 1 << 1) static let CDStereo = Features(rawValue: 1 << 2) static let ChromeWheels = Features(rawValue: 1 << 3) static let PinStripes = Features(rawValue: 1 << 4) static let LeatherInterior = Features(rawValue: 1 << 5) static let Undercoating = Features(rawValue: 1 << 6) static let WindowTint = Features(rawValue: 1 << 7) }
Every other feature is already built in. The default option set implementations mean you inherit initialization (init), set operations (union, intersect, exclusiveOr), membership management (contains, insert, remove), bitwise operations (unionInPlace, intersectInPlace, exclusiveOrInPlace), and more.
In fact, all you need to do is define your flags and you're immediately ready to use them, as in the following examples:
var options : Features = [.AlarmSystem, .Undercoating, .WindowTint] options.contains(.LeatherInterior) // false options.contains(.Undercoating) // true
Viewing Options
Swift 2.0's mirroring automatically converts enumeration representations to printable descriptions. For example:
enum Colors { case Red, Orange, Yellow, Green, Blue, Indigo, Violet } print(Colors.Red) // prints Colors.Red
This convenience does not yet extend to OptionSetType instances. When you print option sets created in this article, they look something like C.UIRectEdge(rawValue: 14) or Features(rawValue: 194). That's both ugly and not particularly helpful.
I doubt this lack of print support will continue for long. The developer community should file sufficient bug reports with Apple to effect change.
Until then, you can create a custom representation using the CustomStringConvertible. This protocol replaces the Printable protocol in Swift 2.0. The following extension applies the new Swift 2.0 for-in-where construct to gather just those flags contained in the active set and returns a string representation of those members.
extension Features : CustomStringConvertible { var description : String { let strings = ["Alarm System", "CD Stereo", "Chrome Wheels", "Pin Stripes", "Leather Interior", "Undercoating", "Window Tint"] var members = [String]() for (flag, string) in strings.enumerate() where contains(Features(rawValue:1<<(flag + 1))) { members.append(string) } return members.description } } print(carOptions) // [Alarm System, Undercoating, Window Tint]
Wrap Up
This article highlights just one of the great new Swift 2.0 features available in Xcode 7. These updates simplify bitflags for existing Cocoa and Cocoa Touch NS_OPTIONs and for your own custom implementations. These changes make option sets more approachable and usable from your Swift code. I encourage you to explore this new feature. It's a simple and very welcome upgrade.