How to create NS_OPTIONS-style bitmask enumeration

2019-01-02 19:39发布

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?

15条回答
素衣白纱
2楼-- · 2019-01-02 20:01

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

查看更多
路过你的时光
3楼-- · 2019-01-02 20:01

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
    }
}
查看更多
零度萤火
4楼-- · 2019-01-02 20:02

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"
        }
    }
}
查看更多
其实,你不懂
5楼-- · 2019-01-02 20:06

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)
}
查看更多
怪性笑人.
6楼-- · 2019-01-02 20:09

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

查看更多
君临天下
7楼-- · 2019-01-02 20:10

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")
}
查看更多
登录 后发表回答