【问题标题】:How to create NS_OPTIONS-style bitmask enumerations in Swift?如何在 Swift 中创建 NS_OPTIONS 样式的位掩码枚举?
【发布时间】:2014-06-05 17:09:54
【问题描述】:

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

再往下说,它是关于NS_OPTIONS-marked C 样式选项的:

Swift 还导入用NS_OPTIONS 宏标记的选项。然而 选项的行为类似于导入的枚举,选项也可以 支持一些按位运算,如&|~。在 Objective-C 中, 您表示一个空选项集,常量为零 (0)。在 Swift,使用nil 表示没有任何选项。

鉴于 Swift 中没有 options 值类型,我们如何创建一个 C 风格的选项变量来使用?

【问题讨论】:

标签: ios bitwise-operators swift


【解决方案1】:

斯威夫特 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 建议使用空数组字面量,而不是提供 none 选项:

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

查看由 Swift 导入的 Objective-C 选项(例如UIViewAutoresizing),我们可以看到选项被声明为符合RawOptionSetType 协议的struct,而后者又符合@ 987654335@、EquatableRawRepresentableBitwiseOperationsTypeNilLiteralConvertible。我们可以像这样创建自己的:

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,就像 Apple 文档中描述的那样:您可以使用类似 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")
}

我已经构建了一个 generator to create a Swift option set 没有所有的查找/替换。

最新: Swift 1.1 beta 3 的修改。

【讨论】:

  • 除非我将value 设为UInt32,否则它对我不起作用。您也不需要定义任何函数,相关函数已经为RawOptionSets 定义(例如func |&lt;T : RawOptionSet&gt;(a: T, b: T) -&gt; T
  • 谢谢,关于函数的要点很重要——我认为编译器在抱怨那些当我没有其他协议一致性时。你发现UInt 有什么问题?对我来说效果很好。
  • 有没有使用 enum 而不是 struct 的解决方案?我需要我的兼容objective-c...
  • @jowie enum CollisionTypes: UInt32 { case Player = 1 case Wall = 2 case Star = 4 case Vortex = 8 case Finish = 16 }
  • 在这种情况下,Apple's docs 非常好。
【解决方案2】:

Xcode 6.1 Beta 2 对RawOptionSetTypeprotocol 进行了一些更改(请参阅此Airspeedvelocity blog entryApple release notes)。

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

【讨论】:

    【解决方案3】:

    文档中的 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]
    }
    

    你可以找到它here

    【讨论】:

      【解决方案4】:

      在 Swift 2(目前作为 Xcode 7 测试版的一部分测试版)中,NS_OPTIONS 样式类型作为新 OptionSetType 类型的子类型导入。并且由于新的Protocol Extensions 功能和OptionSetType 在标准库中的实现方式,您可以声明自己的扩展OptionsSetType 的类型并获得与导入NS_OPTIONS 样式类型相同的所有函数和方法.

      但这些函数不再基于位算术运算符。在 C 中使用一组非独占布尔选项需要在字段中屏蔽和旋转位是一个实现细节。确实,一组选项是一个set...独特项目的集合。所以OptionsSetTypeSetAlgebraType 协议中获取所有方法,例如从数组字面量语法创建、contains 之类的查询、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`
        }
        

        【讨论】:

          【解决方案6】:

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

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

          【讨论】:

          • @ChrisPrince 很可能是因为它是为 Swift 1.0 创建的,此后一直没有更新。
          • 我实际上正在开发一个 Swift 2.0 版本。
          【解决方案7】:

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

          【讨论】:

            【解决方案8】:

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

            创建选项protocol 并重载|&amp;

            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) 
            

            【讨论】:

              【解决方案9】:

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

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

              【讨论】:

                【解决方案10】:

                没有其他人提到过它——经过一些修补后我有点误会了它——但 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 用例来挑战这种不同的范式,其中整数原始值仍然显示出优点。

                【讨论】:

                  【解决方案11】:

                  为了避免对位位置进行硬编码,这在使用(1 &lt;&lt; 0)(1 &lt;&lt; 1)(1 &lt;&lt; 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)
                  }
                  

                  【讨论】:

                  • 刚刚添加了您无需硬编码的示例。
                  【解决方案12】:

                  我使用以下我需要我可以获得的两个值,用于索引数组的 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"
                          }
                      }
                  }
                  

                  【讨论】:

                    【解决方案13】:

                    re:使用带有多个选项的选项集创建沙盒和书签

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

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

                    【讨论】:

                      【解决方案14】:

                      Nate's answer 不错,但我会自己动手做,像这样:

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

                      【讨论】:

                        【解决方案15】:

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

                        【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2014-07-27
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        相关资源
                        最近更新 更多