简体   繁体   English

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

[英]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, use nil 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选项变量来使用?

Swift 3.0 斯威夫特3.0

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

Swift 2.0 斯威夫特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 . 在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")
}

Swift 1.2 斯威夫特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 . 查看Swift导入的Objective-C选项(例如, UIViewAutoresizing ),我们可以看到这些选项被声明为符合协议RawOptionSetTypestruct ,该struct又符合_RawOptionSetTypeEquatableRawRepresentableBitwiseOperationsTypeNilLiteralConvertible 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]
}

You can find it here 你可以在这里找到

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)等,或更糟糕的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)
}

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM