OptionSetType and enums
Edit: I'm surprised at my former self for not saying this upfront at the time, but... instead of trying to force other value types into the OptionSet
protocol (Swift 3 removed Type
from the name), it's probably better to consider the API where you use those types and use Set
collections where appropriate.
OptionSet
types are weird. They are both collections and not collections — you can construct one from multiple flags, but the result is still a single value. (You can do some work to figure out a collection-of-single-flags equivalent to such a value, but depending on the possible values in the type it might not be unique.)
On the other hand, being able to have one something, or more than one unique somethings, can be important to the design of an API. Do you want users to say they have more than one favorite, or enforce that there's only one? Just how many "favorites" do you want to allow? If a user claims multiple favorites, should they be ranked in user-specific order? These are all questions that are hard to answer in an OptionSet
-style type, but much easier if you use a Set
type or other actual collection.
The rest of this answer a) is old, using Swift 2 names, and b) assumes that you're trying to implement OptionSet
anyway, even if it's a bad choice for your API...
See the docs for OptionSetType
:
Supplies convenient conformance to
SetAlgebraType
for any type whoseRawValue
is aBitwiseOperationsType
.
In other words, you can declare OptionSetType
conformance for any type that also adopts RawRepresentable
. However, you gain the magic set-algebra syntax support (via operators and ArrayLiteralConvertible
conformance) if and only if your associated raw value type is one that conforms to BitwiseOperationsType
.
So, if your raw value type is String
, you're out of luck — you don't gain the set algebra stuff because String
doesn't support bitwise operations. (The "fun" thing here, if you can call it that, is that you can extend String
to support BitwiseOperationsType
, and if your implementation satisfies the axioms, you can use strings as raw values for an option set.)
Your second syntax errors at runtime because you've created an infinite recursion — calling self.init(rawValue:)
from init(rawValue:)
keeps gong until it blows the stack.
It's arguably a bug (please file it) that you can even try that without a compile time error. Enums shouldn't be able to declare OptionSetType
conformance, because:
The semantic contract of an enum is that it's a closed set. By declaring your
ProgrammingLanguage
enum you're saying that a value of typeProgrammingLanguage
must be one ofSwift
,Scala
, orHaskell
, and not anything else. A value of "Swift and Scala" isn't in that set.The underlying implementation of an
OptionSetType
is based on integer bitfields. A "Swift and Haskell" value, ([.Swift, .Haskell]
) is really just.Swift.rawValue | .Haskell.rawValue
. This causes trouble if your set of raw values isn't bit-aligned. That is, if.Swift.rawValue == 1 == 0b01
, and.Haskell.rawValue == 2 == 0b10
, the bitwise-or of those is0b11 == 3
, which is the same as.Scala.rawValue
.
TLDR: if you want OptionSetType
conformance, declare a struct.
And use static let
to declare members of your type.
And pick your raw values such that members you want to be distinct from possible (bitwise-or) combinations of other members actually are.
struct ProgrammingLanguage: OptionSetType {
let rawValue: Int
// this initializer is required, but it's also automatically
// synthesized if `rawValue` is the only member, so writing it
// here is optional:
init(rawValue: Int) { self.rawValue = rawValue }
static let Swift = ProgrammingLanguage(rawValue: 0b001)
static let Haskell = ProgrammingLanguage(rawValue: 0b010)
static let Scala = ProgrammingLanguage(rawValue: 0b100)
}
Good ways to keep your values distinct: use binary-literal syntax as above, or declare your values with bit shifts of one, as below:
static let Swift = ProgrammingLanguage(rawValue: 1 << 0)
static let Haskell = ProgrammingLanguage(rawValue: 1 << 1)
static let Scala = ProgrammingLanguage(rawValue: 1 << 2)
Is there a way to define a swift enum with power of 2 (or calculated value)
As @Martin R said, you need struct.
struct Placement: OptionSetType {
let rawValue: Int
init(rawValue: Int) {
self.rawValue = rawValue
}
static let Left = Placement(rawValue: 1 << 0)
static let Right = Placement(rawValue: 1 << 1)
static let Center = Placement(rawValue: 1 << 2)
static let Top = Placement(rawValue: 1 << 3)
}
Options Enum Equivalent in Swift
This is known as an option set, bridged automatically from an Objective C NS_OPTION
. They are effectively a more type-safe way of handling an enum type in which multiple options can be selected. In Swift, these values are not treated like bit masks (as in Objective C and hence why the |
operator is not valid here) but rather as distinct values of an enum. An option set effectively acts like a Set of that enum, providing a type safe bridge with equivalent functionality.
Apple specifies the details of their implementation here:
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html
How to create NS_OPTIONS-style bitmask enumerations in Swift?
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.
Wrapping sets of enums
enum TileTypes
{
GroundTypes = 1000
PropTypes = 2000
}
enum Ground
{
FIRST_GROUND = GroundTypes,
FLAT = FIRST_GROUND,
SLOPE,
...,
INVALID_GROUND
}
enum Props
{
FIRST_PROP = PropTypes,
SIGN = FIRST_PROP,
...,
INVALID_PROP
}
This is the way I usually handle groupable type definitions for persistent structures. The advantages here are:
- Subtypes can be nested as desired
- There exist gaps between values of different subtypes, so that the values of Props are not dependant of the number of Ground types
It is easy to check if a value is of type
bool isGround(int value) { return FIRST_GROUND <= value && INVALID_GROUND > value; }
Easy-to-read and not highly sophisticated syntax
How to pass multiple enum values as a function parameter
Edit: In Swift 3.0:
let options: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading]
Edit: This is how you would use the options enum in Swift 2.0:
let options: NSStringDrawingOptions = [.UsesLineFragmentOrigin, .UsesFontLeading]
Edit: The issue has been resolved in iOS 8.3 SDK Beta 1 (12F5027d):
Modified
NSStringDrawingOptions
[struct]
- From:
enum NSStringDrawingOptions : Int
- To:
struct NSStringDrawingOptions : RawOptionSetType
You can now write:
let options : NSStringDrawingOptions = .UsesLineFragmentOrigin | .UsesFontLeading
After some research and and @Anton Tcholakov's "comment":
If you're targeting OS X 10.10, this is as simple way to do it:
let size = CGSize(width: 280, height: Int.max)
let options : NSStringDrawingOptions = .UsesLineFragmentOrigin | .UsesFontLeading
let boundingRect = string.bridgeToObjectiveC().boundingRectWithSize(size, options: options, attributes: attributes, context: nil)However, in iOS 8 SDK (in the current seed), there's a bug, where
NSStringDrawingOptions
is ported to Swift asenum : Int
, instead ofstruct : RawOptionSet
. You should send a bug report to Apple describing this serious problem.
Related Topics
Adding a View to the Window Hierarchy
Ycombinator Not Working in Swift
Generate an Integer Binary Representation Using Swift
Changing Value in Nested Dictionary in Swift
Swiftui: Navigation Bar Title in Reusable Cross-Platform (iOS & MACos) View
How to Encode Realm's List<> Type
How to Instantiate a Storyboard from a File Within an iOS Playground
How to Make Nsattributedstring Codable Compliant
How to Disambiguate a Type and a Module With the Same Name
How to Get the Realy Fixed Device-Id in Swift
Avspeechutterance - Swift - Initializing with a Phrase
Swift Didset Get Index of Array
Treat *Some* Warnings as Errors in Swift
How to Read and Write Data to a Text File in Swift Using Playground
Nsstatusitem in Nsstatusbar, Action Selector Method Not Responding
Example of Dispatch_Once in Swift
Result Values in '? :' Expression Have Mismatching Types 'Some View' and '...'