如何在Swift中创建NS_OPTIONS样式的位掩码枚举?


137

在Apple有关与C API进行交互的文档中,他们描述了将NS_ENUM带有标记的C样式枚举作为Swift枚举导入的方式。这是有道理的,并且由于Swift中的枚举很容易作为enum值类型提供,因此很容易看到如何创建自己的枚举。

再往下,它说了关于NS_OPTIONS标记C样式的选项:

Swift还会导入标有NS_OPTIONS宏的选项。而选项的行为类似于进口枚举,选项还可以支持一些位操作,如&|~。在Objective-C中,您表示一个空的选项集,其常数为零(0)。在Swift中,用于nil表示没有任何选项。

鉴于optionsSwift中没有值类型,我们如何创建要使用的C-Style选项变量?


3
@Mattt非常有名的“ NSHipster”对以下内容进行了详尽的描述RawOptionsSetTypenshipster.com/rawoptionsettype
Klaas

Answers:


258

斯威夫特3.0

几乎与Swift 2.0相同。OptionSetType重命名为OptionSet,并且按惯例将枚举写为小写。

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

noneSwift 3建议不要提供选项,而只是使用空数组文字:

let noOptions: 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")
}

雨燕2.0

在Swift 2.0中,协议扩展会处理大多数样板文件,这些样板文件现在已作为符合的结构导入OptionSetType。(RawOptionSetType自Swift 2 beta 2起已消失。)声明要简单得多:

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

现在我们可以使用基于集合的语义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")
}

斯威夫特1.2

看着由夫特导入的(Objective-C的选项UIViewAutoresizing,例如),我们可以看到,选项被声明为struct符合协议RawOptionSetType,这反过来又符合 _RawOptionSetTypeEquatableRawRepresentableBitwiseOperationsType,和NilLiteralConvertible。我们可以这样创建自己的:

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

现在,我们可以像对待MyOptions苹果文档中所述的那样对待这个新的选项集:您可以使用enum-like语法:

let opt1 = MyOptions.FirstOption
let opt2: MyOptions = .SecondOption
let opt3 = MyOptions(4)

而且它的行为也像我们期望的选项一样:

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")
}

我已经构建了一个生成器来创建Swift选项集,而无需进行所有查找/替换。

最新:对Swift 1.1 beta 3的修改。


1
除非我做value了一个,否则对我没用UInt32。您也不需要定义任何功能,已经为RawOptionSets 定义了相关功能(例如func |<T : RawOptionSet>(a: T, b: T) -> T
David Lawson 2014年

谢谢,关于功能的要点-我认为当我没有其余协议一致性时,编译器会抱怨这些功能。您看到了什么问题UInt?对我来说很好。
Nate Cook

2
有没有使用枚举而不是struct的解决方案?我需要与我的物镜兼容...
jowie 2015年

1
@jowieenum CollisionTypes: UInt32 { case Player = 1 case Wall = 2 case Star = 4 case Vortex = 8 case Finish = 16 }
mccoyLBI '16

1
在这种情况下,Apple的文档确实很棒。
罗杰斯先生

12

Xcode 6.1 Beta 2对RawOptionSetType协议进行了一些更改(请参阅此Airspeedvelocity博客条目Apple发行说明)。

基于Nate Cooks的示例,这里是更新的解决方案。您可以这样定义自己的选项集:

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

然后可以像这样使用它来定义变量:

let opt1 = MyOptions.FirstOption
let opt2:MyOptions = .SecondOption
let opt3 = MyOptions(4)

像这样测试位:

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")
}

8

文档中的Swift 2.0示例:

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]
}

你可以在这里找到


6

在Swift 2(当前是Xcode 7 beta的一部分中的beta)中,NS_OPTIONS-style类型作为新OptionSetType类型的子类型导入。借助新的协议扩展功能和OptionSetType在标准库中实现的方式,您可以声明自己的扩展类型,OptionsSetType并获得与导入的NS_OPTIONS样式类型相同的所有功能和方法。

但是这些函数不再基于位运算符。在C中使用一组非排他的布尔选项需要屏蔽和旋转字段中的位是实现细节。实际上,一组选项就是一 ...一组唯一项。因此,OptionsSetType可以从SetAlgebraType协议中获取所有方法,例如从数组文字语法创建,像进行查询contains,使用mask进行掩盖intersection等。(无需记住用于哪个成员资格测试的有趣字符!)


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`
}

4

如果您不需要与Objective-C互操作,而只想在Swift中使用位掩码的表面语义,我就编写了一个简单的“库”,称为BitwiseOptions,可以使用常规的Swift枚举来做到这一点,例如:

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

等等。这里没有实际的位被翻转。这些是对不透明值的设置操作。你可以在这里找到要点。


@ChrisPrince最有可能是因为它是为Swift 1.0创建的,此后从未进行过更新。
Gregory Higley

我实际上正在开发这个的Swift 2.0版本。
Gregory Higley

2

如Rickster所述,您可以在Swift 2.0中使用OptionSetType。NS_OPTIONS类型被导入为符合OptionSetType协议,该协议为选项提供了类似于集合的接口:

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

它为您提供了这种工作方式:

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
    }
}

2

如果我们唯一需要的功能是将选项与之合并的方式,|并检查合并的选项是否包含特定的选项,&而Nate Cook的答案可能是这样的:

创建一个选项protocol和重载|以及&

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
    }
}

现在,我们可以更简单地创建选项结构,如下所示:

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

它们可以按如下方式使用:

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

myMethod(options: .One | .Three) 

2

只是为其他想知道您是否可以组合复合选项的人提供一个额外的示例。可以,并且如果您习惯了旧的位域,它们可以像您期望的那样结合在一起:

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

它将集合展平[.AB, .X][.A, .B, .X](至少在语义上):

print(state)      // 0b111 as expected: "State(rawValue: 7)"
print(State.AB)   // 0b11 as expected: "State(rawValue: 3)"

1

没有人提到它-经过一番修补后,我有点误入歧途-但Swift Set似乎运行得很好。

如果我们认为(也许是维恩图?)位掩码实际上表示什么,那么它可能是空集。

当然,从第一个原理解决问题时,我们失去了按位运算符的便利,但是获得了强大的基于集合的方法,从而提高了可读性。

例如,这是我的修补:

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

我觉得这很不错,因为我觉得它来自于解决问题的第一种原理方法,就像Swift一样,而不是尝试采用C风格的解决方案。

我们也想听听一些挑战这种不同范式的Obj-C用例,其中整数原始值仍然显示出优点。


1

为了避免硬编码的比特位置,使用时,其是不可避免的(1 << 0)(1 << 1)(1 << 15)等,或者甚至更糟1216384等,或一些十六进制变化,人们可以首先定义在所述位enum,然后让所述枚举做位序数的计算:

// 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)
}

刚刚添加的示例中,您无需进行任何硬编码。
Peter Ahlberg

1

我使用以下命令,我需要获得两个值:用于索引数组的rawValue和用于标志的value。

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

如果需要更多,只需添加一个计算属性。

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"
        }
    }
}

1

回复:使用带有多个选项的选项集创建沙箱和书签

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

需要组合用于创建的选项的解决方案,在并非所有选项都互斥时有用。


0

Nate的回答很好,但我想自己动手做,就像这样:

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

0

使用选项集类型,快速使用3 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]
}

1
这个答案已经或多或少地涵盖了这一点
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.