How to create NS_OPTIONS-style bitmask enumerations in Swift?
Asked Answered
P

15

140

In Apple's documentation about interacting with C APIs, they describe the way NS_ENUM-marked C-style enumerations are imported as Swift enumerations. This makes sense, and since enumerations in Swift are readily provided as the enum value type it's easy to see how to create our own.

Further down, it says this about NS_OPTIONS-marked C-style options:

Swift also imports options marked with the NS_OPTIONS macro. Whereas options behave similarly to imported enumerations, options can also support some bitwise operations, such as &, |, and ~. In Objective-C, you represent an empty option set with the constant zero (0). In Swift, use nil to represent the absence of any options.

Given that there isn't an options value type in Swift, how can we create a C-Style options variable to work with?

Preterite answered 5/6, 2014 at 17:9 Comment(2)
@Mattt's very famous "NSHipster" has an extensive description of the RawOptionsSetType : nshipster.com/rawoptionsettypeAmimia
Possible duplicate of Declaring and using a bit field enum in SwiftMilk
P
268

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.

Preterite answered 5/6, 2014 at 17:10 Comment(21)
It didn't work for me unless I made value a UInt32. You also don't need to define any of the functions, relevant functions are already defined for RawOptionSets (e.g. func |<T : RawOptionSet>(a: T, b: T) -> T)Guffey
Thanks, great point about the functions -- I think the compiler was complaining about those when I didn't have the rest of the protocol conformance in place. What problems did you see with UInt? It's working fine for me.Preterite
When using it in a playground with UInt I got "fatal error: Can't unwrap Optional.None", but it worked fine with UInt32. Really weird. Anyway, I managed to use classes to take care of all the required functions for a slightly cleaner implementation: davidlawson.com.au/2014/06/implementing-ns_options-in-swiftGuffey
In beta 5, RawOptionSet has been renamed to RawOptionSetType and instead of func getLogicValue() -> Bool it should now be var boolValue:Bool { get { return self.value != 0} }Trocar
Thanks for the heads up! Updated the answer above.Preterite
beta 6 adds the need to implement BitwiseOperationsType.Amimia
@Amimia Grab that code again - Apple dropped BooleanType conformance for some reason, so standard bitmask checks aren't working.Preterite
@NateCook, how do you know which functions to implement? I have been looking for the BitwiseOperationsType protocol but can't find it anywhere. Please enlighten me.Kroo
@Kroo Do you know how to view the Swift "header file"? You can follow these instructions to get there, then just do a search.Preterite
Thanks @NateCook, should have thought of that but the documentation is so good I usually never bother to look at the header files. Didn't cross my mind, D'Oh.Kroo
Thanks @NateCook, I suggest add in struct func has(option: MyOptions) -> Bool { return self & option != nil }Anatropous
There is also a much more readable way of initialising your bitmask values. It is available in the Swift Standard Library Reference here, which may have been updated since this answer was posted.Hearsay
@MattQuiros Yep, that's a much better way - however, it's only available as part of Swift 2.0.Sashenka
Is there a solution that uses enum instead of struct? I need mine to be compatible with objective-c...Intake
@Intake enum CollisionTypes: UInt32 { case Player = 1 case Wall = 2 case Star = 4 case Vortex = 8 case Finish = 16 }Coppins
How can I get the raw value from a MyOptions array? In the example let multipleOptions: MyOptions = [.FirstOption, .SecondOption] I wish to get 3 from multipleOptions. Right now I am writing my own function to do this.Revers
@FranklinYu: Use the option set's rawValue property.Preterite
@NateCook, thank you. I forgot to cast the MyOptions array back to MyOptions, since an array does not have a rawValue property.Revers
Great answer, this really helped me. in the swift 2.0 section, are you missing this part? init(rawValue: Int) { self.rawValue = rawValue } Or is it not needed because it is a struct?Mastership
@cmarti1138 Right, that init gets added automatically. However, if you add any other initializers you'll need to add it manually.Preterite
In this instance, Apple's docs are really good.Uncouple
A
13

Xcode 6.1 Beta 2 brought some changes to the RawOptionSetTypeprotocol (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")
}
Amimia answered 30/9, 2014 at 21:14 Comment(0)
U
8

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

Unrig answered 28/6, 2015 at 11:1 Comment(0)
G
6

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!)

Gladine answered 8/6, 2015 at 23:1 Comment(0)
D
5
//Swift 2.0
 //create
    struct Direction : OptionSetType {
        let rawValue: Int
        static let None   = Direction(rawValue: 0)
        static let Top    = Direction(rawValue: 1 << 0)
        static let Bottom = Direction(rawValue: 1 << 1)
        static let Left   = Direction(rawValue: 1 << 2)
        static let Right  = Direction(rawValue: 1 << 3)
    }
//declare
var direction: Direction = Direction.None
//using
direction.insert(Direction.Right)
//check
if direction.contains(.Right) {
    //`enter code here`
}
Diacritical answered 15/12, 2015 at 4:33 Comment(0)
Q
4

If you don't need to interoperate with Objective-C and just want the surface semantics of bit masks in Swift, I've written a simple "library" called BitwiseOptions that can do this with regular Swift enumerations, e.g.:

enum Animal: BitwiseOptionsType {
    case Chicken
    case Cow
    case Goat
    static let allOptions = [.Chicken, .Cow, .Goat]
}

var animals = Animal.Chicken | Animal.Goat
animals ^= .Goat
if animals & .Chicken == .Chicken {
    println("Chick-Fil-A!")
}

and so on. No actual bits are being flipped here. These are set operations on opaque values. You can find the gist here.

Quinze answered 24/11, 2014 at 10:24 Comment(2)
@ChrisPrince Most likely that's because it was created for Swift 1.0 and hasn't been updated since.Quinze
I am actually working on a Swift 2.0 version of this.Quinze
M
2

If the only functionality we are needing is a way to combine options with | and check if combined options contain a particular option with & an alternative to Nate Cook's answer could be this:

Create an options protocol and overload | and &:

protocol OptionsProtocol {

    var value: UInt { get }
    init (_ value: UInt)

}

func | <T: OptionsProtocol>(left: T, right: T) -> T {
    return T(left.value | right.value)
}

func & <T: OptionsProtocol>(left: T, right: T) -> Bool {
    if right.value == 0 {
        return left.value == 0
    }
    else {
        return left.value & right.value == right.value
    }
}

Now we can create options structs more simply like so:

struct MyOptions: OptionsProtocol {

    private(set) var value: UInt
    init (_ val: UInt) {value = val}

    static var None: MyOptions { return self(0) }
    static var One: MyOptions { return self(1 << 0) }
    static var Two: MyOptions { return self(1 << 1) }
    static var Three: MyOptions { return self(1 << 2) }
}

They can be used as follows:

func myMethod(#options: MyOptions) {
    if options & .One {
        // Do something
    }
}

myMethod(options: .One | .Three) 
Meandrous answered 9/10, 2014 at 9:28 Comment(0)
B
2

As Rickster already mentioned, you can use OptionSetType in Swift 2.0. NS_OPTIONS types get imported as conforming to the OptionSetType protocol, which presents a set-like interface for options:

struct CoffeeManipulators : OptionSetType {
    let rawValue: Int
    static let Milk     = CoffeeManipulators(rawValue: 1)
    static let Sugar    = CoffeeManipulators(rawValue: 2)
    static let MilkAndSugar = [Milk, Sugar]
}

It gives you this way of working:

struct Coffee {
    let manipulators:[CoffeeManipulators]

    // You can now simply check if an option is used with contains
    func hasMilk() -> Bool {
        return manipulators.contains(.Milk)
    }

    func hasManipulators() -> Bool {
        return manipulators.count != 0
    }
}
Blotchy answered 10/6, 2015 at 17:40 Comment(0)
B
2

Just posting an extra example for anyone else who was wondering if you could combine compound options. You can, and they combine like you'd expect if you're used to good old bitfields:

struct State: OptionSetType {
    let rawValue: Int
    static let A      = State(rawValue: 1 << 0)
    static let B      = State(rawValue: 1 << 1)
    static let X      = State(rawValue: 1 << 2)

    static let AB:State  = [.A, .B]
    static let ABX:State = [.AB, .X]    // Combine compound state with .X
}

let state: State = .ABX
state.contains(.A)        // true
state.contains(.AB)       // true

It flattens the set [.AB, .X] into [.A, .B, .X] (at least semantically):

print(state)      // 0b111 as expected: "State(rawValue: 7)"
print(State.AB)   // 0b11 as expected: "State(rawValue: 3)"
Barnum answered 2/3, 2016 at 3:38 Comment(0)
I
1

No one else has mentioned it -- and I kind of blundered onto it after some tinkering -- but a Swift Set seems to work fairly well.

If we think (maybe to a Venn diagram?) about what a bit mask is actually representing, it is a possibly empty set.

Of course, in approaching the problem from first principles, we lose the convenience of bitwise operators, but gain powerful set-based methods which improves readability.

Here is my tinkering for example:

enum Toppings : String {
    // Just strings 'cause there's no other way to get the raw name that I know of...
    // Could be 1 << x too...
    case Tomato = "tomato"
    case Salami = "salami"
    case Cheese = "cheese"
    case Chicken = "chicken"
    case Beef = "beef"
    case Anchovies = "anchovies"

    static let AllOptions: Set<Toppings> = [.Tomato, .Salami, .Cheese, .Chicken, .Anchovies, .Beef]
}

func checkPizza(toppings: Set<Toppings>) {
    if toppings.contains(.Cheese) {
        print("Possible dairy allergies?")
    }

    let meats: Set<Toppings> = [.Beef, .Chicken, .Salami]
    if toppings.isDisjointWith(meats) {
        print("Vego-safe!")
    }
    if toppings.intersect(meats).count > 1 {
        print("Limit one meat, or 50¢ extra charge!")
    }

    if toppings == [Toppings.Cheese] {
        print("A bit boring?")
    }
}

checkPizza([.Tomato, .Cheese, .Chicken, .Beef])

checkPizza([.Cheese])

I find this nice because I feel it comes from a first principles approach to the problem -- much like Swift -- rather than trying to adapt C-style solutions.

Would also like to hear some Obj-C use cases that would challenge this different paradigm, where the integer raw values still shows merit.

Irrational answered 2/5, 2016 at 11:42 Comment(0)
M
1

In order to avoid hard coding the bit positions, which is unavoidable when using (1 << 0), (1 << 1), (1 << 15) etc. or even worse 1, 2, 16384 etc. or some hexadecimal variation, one could first defines the bits in an enum, then let said enum do the bit ordinal calculation:

// Bits
enum Options : UInt {
    case firstOption
    case secondOption
    case thirdOption
}

// Byte
struct MyOptions : OptionSet {
    let rawValue: UInt

    static let firstOption  = MyOptions(rawValue: 1 << Options.firstOption.rawValue)
    static let secondOption = MyOptions(rawValue: 1 << Options.secondOption.rawValue)
    static let thirdOption  = MyOptions(rawValue: 1 << Options.thirdOption.rawValue)
}
Maund answered 19/7, 2017 at 4:2 Comment(1)
Just added example where you don't have to hard code anything.Milk
M
1

I use the following I need the both values I can get, rawValue for indexing arrays and value for flags.

enum MyEnum: Int {
    case one
    case two
    case four
    case eight

    var value: UInt8 {
        return UInt8(1 << self.rawValue)
    }
}

let flags: UInt8 = MyEnum.one.value ^ MyEnum.eight.value

(flags & MyEnum.eight.value) > 0 // true
(flags & MyEnum.four.value) > 0  // false
(flags & MyEnum.two.value) > 0   // false
(flags & MyEnum.one.value) > 0   // true

MyEnum.eight.rawValue // 3
MyEnum.four.rawValue  // 2

And if one needs more just add a computed property.

enum MyEnum: Int {
    case one
    case two
    case four
    case eight

    var value: UInt8 {
        return UInt8(1 << self.rawValue)
    }

    var string: String {
        switch self {
        case .one:
            return "one"
        case .two:
            return "two"
        case .four:
            return "four"
        case .eight:
            return "eight"
        }
    }
}
Milk answered 23/7, 2017 at 19:2 Comment(0)
R
1

re: Sandbox and bookmark creations using option sets with several options

let options:NSURL.BookmarkCreationOptions = [.withSecurityScope,.securityScopeAllowOnlyReadAccess]
let temp = try link.bookmarkData(options: options, includingResourceValuesForKeys: nil, relativeTo: nil)

solution to needing to combine options for creations, useful when not all options are mutually exclusive.

Racketeer answered 4/11, 2017 at 17:56 Comment(0)
J
0

Nate's answer is good but I would make it DIY, like so:

struct MyOptions : OptionSetType {
    let rawValue: Int

    static let None         = Element(rawValue: 0)
    static let FirstOption  = Element(rawValue: 1 << 0)
    static let SecondOption = Element(rawValue: 1 << 1)
    static let ThirdOption  = Element(rawValue: 1 << 2)
}
Jobye answered 11/9, 2016 at 2:48 Comment(0)
Z
0

Use an Option Set Type, in swift 3 use OptionSet

struct ShippingOptions: OptionSet {
    let rawValue: Int

    static let nextDay    = ShippingOptions(rawValue: 1 << 0)
    static let secondDay  = ShippingOptions(rawValue: 1 << 1)
    static let priority   = ShippingOptions(rawValue: 1 << 2)
    static let standard   = ShippingOptions(rawValue: 1 << 3)

    static let express: ShippingOptions = [.nextDay, .secondDay]
    static let all: ShippingOptions = [.express, .priority, .standard]
}
Zins answered 6/6, 2017 at 2:56 Comment(1)
This is more or less already covered in this answer.Binturong

© 2022 - 2024 — McMap. All rights reserved.