如何在Swift中创buildNS_OPTIONS风格的位掩码枚举?

在Apple有关与C API交互的文档中,他们描述了NS_ENUM标记的C样式枚举被导入为Swift枚举的方式。 这是有道理的,由于Swift枚举容易提供enum值types,很容易看到如何创build自己的。

更进一步说,这是关于NS_OPTIONS标记的C风格选项的说明:

Swift也导入标有NS_OPTIONSmacros的选项。 尽pipe选项的行为与导入的枚举类似,但选项也可以支持一些按位操作,例如&|~ 。 在Objective-C中,表示一个用常量零( 0 )设置的空白选项。 在Swift中,使用nil来表示没有任何选项。

鉴于在Swift中没有options值types,我们如何创build一个C样式选项variables来处理?

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

Swift 3的build议不是提供一个none选项,而是简单地使用一个空的数组字面:

 let noOptions: MyOption = [] 

其他用法:

 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

在Swift 2.0中,协议扩展负责处理大部分的样板,这些样板现在被导入为符合OptionSetType的结构。 (从Swift 2 Beta 2开始, RawOptionSetType已经消失)。声明要简单得多:

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

Swift 1.2

看看Swift导入的Objective-C选项(例如UIViewAutoresizing ),我们可以看到选项被声明为符合协议RawOptionSetTypestruct ,该协议依次符合_RawOptionSetTypeEquatableRawRepresentableBitwiseOperationsTypeNilLiteralConvertible 。 我们可以像这样创build我们自己的:

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

现在我们可以像对待Apple的文档中所描述的那样对待这个新的选项集MyOptions :您可以使用类似enum的语法:

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

我已经构build了一个生成器来创buildSwift选项集,但没有find/replace。

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

Xcode 6.1 Beta 2给RawOptionSetType协议带来了一些变化(参见这个Airspeedvelocity博客条目和Apple发行说明 )。

基于Nate Cooks的例子,这是一个更新的解决scheme。 你可以像这样定义你自己的选项集:

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

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

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

像这样testing一下位

 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示例:

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

你可以在这里find它

在Swift 2中(当前beta是Xcode 7 beta的一部分), NS_OPTIONS样式types被导入为新的OptionSetTypetypes的子types。 由于新的协议扩展function以及OptionSetType在标准库中的实现方式,您可以声明自己的扩展OptionsSetType的types,并获取所有导入NS_OPTIONS风格types的函数和方法。

但是这些函数不再是以位运算符为基础的。 在C语言中使用一组非独占的布尔选项需要在字段中掩码和旋转位是一个实现细节。 真的,一组选项是一独特的项目。 所以SetAlgebraTypeSetAlgebraType协议中获取所有的方法,比如从数组字面语法创build, contains查询,用intersection掩码等等(不必再记得哪个有趣的字符用于哪个成员testing!)

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

如果您不需要与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!") } 

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

如果我们需要的唯一function是将选项与|组合的方式 并检查组合的选项是否包含一个特定的选项与替代内特·库克的答案可能是这样的:

创build一个选项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 } } 

现在我们可以更简单地创build选项结构:

 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) 

正如Rickster已经提到的,你可以在Swift 2.0中使用OptionSetType 。 NS_OPTIONStypes被导入为符合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 } } 

只是发布一个额外的例子,谁想知道如果你可以结合复合选项的其他人。 你可以,如果你习惯了旧的位域,他们就像你所期望的那样结合在一起:

 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 

它将set [.AB, .X]变成[.A, .B, .X] (至less在语义上):

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

其他人都没有提到它 – 我经过一番修改之后就大失所望了,但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风格的解决scheme。

还想听听一些Obj-C用例会挑战这种不同的范式,整数原始值仍然显示优点。

为了避免在使用(1 << 0)(1 << 1)(1 << 15)等或甚至更差16384等或某些hex时不可避免的比特位置的硬编码可以先定义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) } 

Nate的答案很好,但我会把它做成DIY,就像这样:

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

使用Option Set Type,在Swift 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] } 

我使用以下我需要两个值我可以得到,rawValue索引数组和标志值。

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

重新:沙箱和书签创作使用选项集与几个选项

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

解决scheme需要结合选项的创作,有用的时候不是所有的选项是相互排斥的。