[英]How to create NS_OPTIONS-style bitmask enumerations in Swift?
In Apple's documentation about interacting with C APIs, they describe the way NS_ENUM
-marked C-style enumerations are imported as Swift enumerations. 在Apple有关与C API交互的文档中,它们描述了将
NS_ENUM
标记的C样式枚举作为Swift枚举导入的方式。 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. 这是有道理的,并且由于Swift中的
enum
很容易作为enum
值类型提供,因此很容易看到如何创建自己的enum
。
Further down, it says this about NS_OPTIONS
-marked C-style options: 再往下,它说了关于
NS_OPTIONS
标记的C样式选项的内容:
Swift also imports options marked with the
NS_OPTIONS
macro.Swift还会导入标有
NS_OPTIONS
宏的选项。 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
).在Objective-C中,您表示一个常量为零(
0
)的空选项集。 In Swift, usenil
to represent the absence of any options.在Swift中,使用
nil
表示不存在任何选项。
Given that there isn't an options
value type in Swift, how can we create a C-Style options variable to work with? 鉴于Swift中没有
options
值类型,我们如何创建一个C-Style选项变量来使用?
Almost identical to Swift 2.0. 与Swift 2.0几乎相同。 OptionSetType was renamed to OptionSet and enums are written lower case by convention.
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)
}
Instead of providing a none
option, the Swift 3 recommendation is to simply use an empty array literal: Swift 3建议不要使用
none
选项,而只是使用一个空数组文字:
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")
}
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
. 在Swift 2.0中,协议扩展会处理大多数样板文件,这些样板文件现在已作为符合
OptionSetType
的结构导入。 ( RawOptionSetType
has disappeared as of Swift 2 beta 2.) The declaration is far simpler: (从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)
}
Now we can use set-based semantics with MyOptions
: 现在我们可以在
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")
}
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
. 查看Swift导入的Objective-C选项(例如,
UIViewAutoresizing
),我们可以看到这些选项被声明为符合协议RawOptionSetType
的struct
,该struct
又符合_RawOptionSetType
, Equatable
, RawRepresentable
, BitwiseOperationsType
和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: 现在,我们可以像对待苹果文档中所述的那样对待这个新选项集
MyOptions
:您可以使用类似enum
的语法:
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. 我已经构建了一个生成器来创建Swift选项集,而无需进行所有查找/替换。
Latest: Modifications for Swift 1.1 beta 3. 最新:对Swift 1.1 beta 3的修改。
Xcode 6.1 Beta 2 brought some changes to the RawOptionSetType
protocol (see this Airspeedvelocity blog entry and the Apple release notes ). Xcode 6.1 Beta 2对
RawOptionSetType
协议进行了一些更改(请参阅此Airspeedvelocity博客条目和Apple发行说明 )。
Based on Nate Cooks example here is an updated solution. 基于Nate Cooks的示例,这里是更新的解决方案。 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")
}
Swift 2.0 example from the documentation: 文档中的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]
}
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. 在Swift 2(当前为Xcode 7 beta的一部分)中,
NS_OPTIONS
样式类型作为新OptionSetType
类型的子类型导入。 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. 并且由于有了新的协议扩展功能以及在标准库中实现
OptionSetType
的方式,您可以声明自己的类型来扩展OptionsSetType
并获得与导入的NS_OPTIONS
-style类型相同的所有函数和方法。
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.
在C中使用一组非排他的布尔选项需要屏蔽和旋转字段中的位,这是实现细节。 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!) 所以
OptionsSetType
得到所有从方法SetAlgebraType
协议从数组文本语法,就像创建,查询,如contains
,以掩盖intersection
等(没有更多不必记住要使用哪个成员资格测试其搞笑本色!)
//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`
}
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, eg: 如果您不需要与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!")
}
and so on. 等等。 No actual bits are being flipped here.
这里没有实际的位被翻转。 These are set operations on opaque values.
这些是对不透明值的设置操作。 You can find the gist here .
你可以在这里找到要点。
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: 并检查组合选项是否包含带有
&
的特定选项,并且Nate Cook的答案的替代方案可能是这样的:
Create an options protocol
and overload |
创建一个期权
protocol
及过载|
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)
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): 它将集合
[.AB, .X]
为[.A, .B, .X]
(至少在语义上):
print(state) // 0b111 as expected: "State(rawValue: 7)"
print(State.AB) // 0b11 as expected: "State(rawValue: 3)"
As Rickster already mentioned, you can use OptionSetType in Swift 2.0. 如Rickster所述,您可以在Swift 2.0中使用OptionSetType 。 NS_OPTIONS types get imported as conforming to the
OptionSetType
protocol, which presents a set-like interface for options: 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]
}
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
}
}
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. 没有人提到它-经过一番修改后,我有点误入歧途-但Swift Set似乎运行得很好。
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. 我觉得这很不错,因为我觉得它来自于解决问题的第一种原理方法,就像Swift一样,而不是尝试采用C风格的解决方案。
Would also like to hear some Obj-C use cases that would challenge this different paradigm, where the integer raw values still shows merit. 我们也想听听一些挑战这种不同范式的Obj-C用例,其中整数原始值仍然显示出优点。
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: 为了避免硬编码的比特位置,使用时,这是无法避免的
(1 << 0)
(1 << 1)
(1 << 15)
等,或更糟糕的1
, 2
, 16384
等,或一些十六进制变体,首先可以定义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)
}
I use the following I need the both values I can get, rawValue for indexing arrays and value for flags. 我使用以下命令,我需要获得两个值:用于索引数组的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
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"
}
}
}
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. 需要组合用于创建的选项的解决方案,在并非所有选项都互斥时有用。
Nate's answer is good but I would make it DIY, like so: 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)
}
Use an Option Set Type, in swift 3 use OptionSet
使用选项设置类型,在迅速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]
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.