How to create NS_OPTIONS-style bitmask enumerations in Swift?
Xcode 6.1 Beta 2 brought some changes to the RawOptionSetType
protocol (see this Airspeedvelocity blog entry and the Apple release notes).
Based on Nate Cooks example here is an updated solution. You can define your own option set like this:
struct MyOptions : RawOptionSetType, BooleanType {
private var value: UInt
init(_ rawValue: UInt) { self.value = rawValue }
// MARK: _RawOptionSetType
init(rawValue: UInt) { self.value = rawValue }
// MARK: NilLiteralConvertible
init(nilLiteral: ()) { self.value = 0}
// MARK: RawRepresentable
var rawValue: UInt { return self.value }
// MARK: BooleanType
var boolValue: Bool { return self.value != 0 }
// MARK: BitwiseOperationsType
static var allZeros: MyOptions { return self(0) }
// MARK: User defined bit values
static var None: MyOptions { return self(0) }
static var FirstOption: MyOptions { return self(1 << 0) }
static var SecondOption: MyOptions { return self(1 << 1) }
static var ThirdOption: MyOptions { return self(1 << 2) }
static var All: MyOptions { return self(0b111) }
}
It can then be used like this to define variables:
let opt1 = MyOptions.FirstOption
let opt2:MyOptions = .SecondOption
let opt3 = MyOptions(4)
And like this to test for bits:
let singleOption = MyOptions.FirstOption
let multipleOptions: MyOptions = singleOption | .SecondOption
if multipleOptions & .SecondOption {
println("multipleOptions has SecondOption")
}
let allOptions = MyOptions.All
if allOptions & .ThirdOption {
println("allOptions has ThirdOption")
}
Swift 2.0 example from the documentation:
struct PackagingOptions : OptionSetType {
let rawValue: Int
init(rawValue: Int) { self.rawValue = rawValue }
static let Box = PackagingOptions(rawValue: 1)
static let Carton = PackagingOptions(rawValue: 2)
static let Bag = PackagingOptions(rawValue: 4)
static let Satchel = PackagingOptions(rawValue: 8)
static let BoxOrBag: PackagingOptions = [Box, Bag]
static let BoxOrCartonOrBag: PackagingOptions = [Box, Carton, Bag]
}
You can find it here
In Swift 2 (currently beta as part of the Xcode 7 beta), NS_OPTIONS
-style types are imported as subtypes of the new OptionSetType
type. And thanks to the new Protocol Extensions feature and the way OptionSetType
is implemented in the standard library, you can declare your own types that extend OptionsSetType
and get all the same functions and methods that imported NS_OPTIONS
-style types get.
But those functions aren't based on bitwise arithmetic operators anymore. That working with a set of non-exclusive Boolean options in C requires masking and twiddling bits in a field is an implementation detail. Really, a set of options is a set... a collection of unique items. So OptionsSetType
gets all the methods from the SetAlgebraType
protocol, like creation from array literal syntax, queries like contains
, masking with intersection
, etc. (No more having to remember which funny character to use for which membership test!)
Swift 3.0
Almost identical to Swift 2.0. OptionSetType was renamed to OptionSet and enums are written lower case by convention.
struct MyOptions : OptionSet {
let rawValue: Int
static let firstOption = MyOptions(rawValue: 1 << 0)
static let secondOption = MyOptions(rawValue: 1 << 1)
static let thirdOption = MyOptions(rawValue: 1 << 2)
}
Instead of providing a none
option, the Swift 3 recommendation is to simply use an empty array literal:
let noOptions: MyOptions = []
Other usage:
let singleOption = MyOptions.firstOption
let multipleOptions: MyOptions = [.firstOption, .secondOption]
if multipleOptions.contains(.secondOption) {
print("multipleOptions has SecondOption")
}
let allOptions = MyOptions(rawValue: 7)
if allOptions.contains(.thirdOption) {
print("allOptions has ThirdOption")
}
Swift 2.0
In Swift 2.0, protocol extensions take care of most of the boilerplate for these, which are now imported as a struct that conforms to OptionSetType
. (RawOptionSetType
has disappeared as of Swift 2 beta 2.) The declaration is far simpler:
struct MyOptions : OptionSetType {
let rawValue: Int
static let None = MyOptions(rawValue: 0)
static let FirstOption = MyOptions(rawValue: 1 << 0)
static let SecondOption = MyOptions(rawValue: 1 << 1)
static let ThirdOption = MyOptions(rawValue: 1 << 2)
}
Now we can use set-based semantics with MyOptions
:
let singleOption = MyOptions.FirstOption
let multipleOptions: MyOptions = [.FirstOption, .SecondOption]
if multipleOptions.contains(.SecondOption) {
print("multipleOptions has SecondOption")
}
let allOptions = MyOptions(rawValue: 7)
if allOptions.contains(.ThirdOption) {
print("allOptions has ThirdOption")
}
Swift 1.2
Looking at the Objective-C options that were imported by Swift (UIViewAutoresizing
, for example), we can see that options are declared as a struct
that conforms to protocol RawOptionSetType
, which in turn conforms to _RawOptionSetType
, Equatable
, RawRepresentable
, BitwiseOperationsType
, and NilLiteralConvertible
. We can create our own like this:
struct MyOptions : RawOptionSetType {
typealias RawValue = UInt
private var value: UInt = 0
init(_ value: UInt) { self.value = value }
init(rawValue value: UInt) { self.value = value }
init(nilLiteral: ()) { self.value = 0 }
static var allZeros: MyOptions { return self(0) }
static func fromMask(raw: UInt) -> MyOptions { return self(raw) }
var rawValue: UInt { return self.value }
static var None: MyOptions { return self(0) }
static var FirstOption: MyOptions { return self(1 << 0) }
static var SecondOption: MyOptions { return self(1 << 1) }
static var ThirdOption: MyOptions { return self(1 << 2) }
}
Now we can treat this new option set, MyOptions
, just like described in Apple's documentation: you can use enum
-like syntax:
let opt1 = MyOptions.FirstOption
let opt2: MyOptions = .SecondOption
let opt3 = MyOptions(4)
And it also behaves like we'd expect options to behave:
let singleOption = MyOptions.FirstOption
let multipleOptions: MyOptions = singleOption | .SecondOption
if multipleOptions & .SecondOption != nil { // see note
println("multipleOptions has SecondOption")
}
let allOptions = MyOptions.fromMask(7) // aka .fromMask(0b111)
if allOptions & .ThirdOption != nil {
println("allOptions has ThirdOption")
}
I've built a generator to create a Swift option set without all the find/replacing.
Latest: Modifications for Swift 1.1 beta 3.