【问题标题】:Declaring and using a bit field enum in Swift在 Swift 中声明和使用位域枚举
【发布时间】:2014-07-29 12:36:18
【问题描述】:

在 Swift 中应该如何声明和使用位域?

像这样声明一个枚举确实有效,但尝试将 2 个值组合在一起无法编译:

enum MyEnum: Int
{
    case One =      0x01
    case Two =      0x02
    case Four =     0x04
    case Eight =    0x08
}

// This works as expected
let m1: MyEnum = .One

// Compiler error: "Could not find an overload for '|' that accepts the supplied arguments"
let combined: MyEnum = MyEnum.One | MyEnum.Four

我查看了 Swift 如何导入 Foundation 枚举类型,它通过定义符合 RawOptionSet 协议的 struct 来做到这一点:

struct NSCalendarUnit : RawOptionSet {
    init(_ value: UInt)
    var value: UInt
    static var CalendarUnitEra: NSCalendarUnit { get }
    static var CalendarUnitYear: NSCalendarUnit { get }
    // ...
}

RawOptionSet 协议是:

protocol RawOptionSet : LogicValue, Equatable {
    class func fromMask(raw: Self.RawType) -> Self
}

但是,没有关于此协议的文档,我自己也不知道如何实现它。此外,不清楚这是否是官方的 Swift 实现位字段的方式,或者这只是 Objective-C 桥如何表示它们。

【问题讨论】:

  • 你试过let combined: MyEnum = MyEnum.One | MyEnum.Four
  • 它给出了一个更重要的错误信息,我会更新这个问题。感谢您的提醒!
  • 有趣。我可以得到或接受参数的唯一方法是let combined = 0x01 | 0x04
  • 值得一提的是MyEnum.One != 0x01如果要获取实际数值,则需要使用MyEnum.One.toRaw()

标签: enums swift bit-fields


【解决方案1】:

他们在其中一个 WWDC 视频中展示了如何做到这一点。

let combined = MyEnum.One.toRaw() | MyEnum.Four.toRaw()

请注意,combined 将是 Int 类型,如果您指定 let combined: MyEnum,实际上会出现编译器错误。这是因为 0x05 没有枚举值,这是表达式的结果。

【讨论】:

  • 赞成这个,因为它解决了我使用内置枚举类型的问题(不是我自己的)。使用 .RawValue,然后 |,然后从原始值初始化回枚举类型,以将其传递给调用,而 Swift 不会抱怨。
【解决方案2】:

我猜想这就是他们在 Foundation 中建模枚举选项的方式:

struct TestOptions: RawOptionSet {

    // conform to RawOptionSet
    static func fromMask(raw: UInt) -> TestOptions {
        return TestOptions(raw)
    }

    // conform to LogicValue
    func getLogicValue() -> Bool {
        if contains([1, 2, 4], value) {
            return true
        }
        return false
    }

    // conform to RawRepresentable
    static func fromRaw(raw: UInt) -> TestOptions? {
        if contains([1, 2, 4], raw) {
            return TestOptions(raw)
        }
        return nil
    }
    func toRaw() -> UInt {
        return value
    }

    // options and value
    var value: UInt
    init(_ value: UInt) {
        self.value = value
    }

    static var OptionOne: TestOptions {
        return TestOptions(1)
    }
    static var OptionTwo: TestOptions {
        return TestOptions(2)
    }
    static var OptionThree: TestOptions {
        return TestOptions(4)
    }
}

let myOptions = TestOptions.OptionOne | TestOptions.OptionThree
println("myOptions: \(myOptions.toRaw())")
if (myOptions & TestOptions.OptionOne) {
    println("OPTION ONE is in there")
} else {
    println("nope, no ONE")
}
if (myOptions & TestOptions.OptionTwo) {
    println("OPTION TWO is in there")
} else {
    println("nope, no TWO")
}
if (myOptions & TestOptions.OptionThree) {
    println("OPTION THREE is in there")
} else {
    println("nope, no THREE")
}

let nextOptions = myOptions | TestOptions.OptionTwo
println("options: \(nextOptions.toRaw())")
if (nextOptions & TestOptions.OptionOne) {
    println("OPTION ONE is in there")
} else {
    println("nope, no ONE")
}
if (nextOptions & TestOptions.OptionTwo) {
    println("OPTION TWO is in there")
} else {
    println("nope, no TWO")
}
if (nextOptions & TestOptions.OptionThree) {
    println("OPTION THREE is in there")
} else {
    println("nope, no THREE")
}

...myOptionsnextOptions 的类型是 TestOptions - 我不确定 fromMask()getLogicValue() 应该如何在这里行动(我只是做了一些最好的猜测),也许有人可以把它捡起来解决?

【讨论】:

    【解决方案3】:

    您可以构建一个符合RawOptionSet 协议的struct,并且您可以像使用内置enum 类型一样使用它,但也可以使用位掩码功能。这里的答案显示了如何: Swift NS_OPTIONS-style bitmask enumerations.

    【讨论】:

    • 问题是,结构不能桥接到 Objective-C。
    • @jowie:这不是严格正确的,因为桥接到 Obj-C 的标准库类型都是结构,但它们使用私有 API。如果你需要在 Objective-C 中工作的东西,你最好的选择是使用 NS_OPTIONS 宏在那里定义它——它将作为 OptionSetType 导入到 Swift 中,你将能够在两侧使用它桥。
    【解决方案4】:

    您必须在每个成员之后使用 .toRaw():

    let combined: Int = MyEnum.One.toRaw() | MyEnum.Four.toRaw()
    

    会起作用。因为你只是想分配一个 MyEnum 类型的“One”,不是一个整数。正如Apple's documentation 所说:

    “与 C 和 Objective-C 不同,Swift 枚举成员在创建时不会被分配默认整数值。在 CompassPoints 示例中,North、South、East 和 West 并不隐式等于 0、1、2 和 3。相反,不同的枚举成员本身就是完全成熟的值,具有明确定义的 CompassPoint 类型。

    因此,如果您希望成员表示其他类型,则必须使用原始值,如 here 所述:

    枚举成员可以预先填充默认值(称为原始值),它们都是相同类型的。特定枚举成员的原始值始终相同。原始值可以是字符串、字符或任何整数或浮点数类型。每个原始值在其枚举声明中必须是唯一的。当整数用于原始值时,如果没有为某些枚举成员指定值,它们会自动递增。使用其 toRaw 方法访问枚举成员的原始值。

    【讨论】:

      【解决方案5】:

      @Mattt 非常有名的“NSHipster”对RawOptionsSetType 进行了广泛的详细描述:http://nshipster.com/rawoptionsettype/

      它包括一个方便的 Xcode 截图:

      struct <# Options #> : RawOptionSetType, BooleanType {
          private var value: UInt = 0
          init(_ value: UInt) { self.value = value }
          var boolValue: Bool { return value != 0 }
          static func fromMask(raw: UInt) -> <# Options #> { return self(raw) }
          static func fromRaw(raw: UInt) -> <# Options #>? { return self(raw) }
          func toRaw() -> UInt { return value }
          static var allZeros: <# Options #> { return self(0) }
          static func convertFromNilLiteral() -> <# Options #> { return self(0) }
      
          static var None: <# Options #>          { return self(0b0000) }
          static var <# Option #>: <# Options #>  { return self(0b0001) }
          // ...
      }
      

      【讨论】:

        【解决方案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

        【讨论】:

        【解决方案7】:

        我认为这里的某些答案可能因过于复杂的解决方案而过时?这对我来说很好..

        enum MyEnum: Int  {
        
            case One = 0
            case Two = 1
            case Three = 2
            case Four = 4
            case Five = 8
            case Six = 16
        
        }
        
        let enumCombined = MyEnum.Five.rawValue | MyEnum.Six.rawValue
        
        if enumCombined & MyEnum.Six.rawValue != 0 {
            println("yay") // prints
        }
        
        if enumCombined & MyEnum.Five.rawValue != 0 {
            println("yay again") // prints
        }
        
        if enumCombined & MyEnum.Two.rawValue != 0 {
            println("shouldn't print") // doesn't print
        }
        

        【讨论】:

        • 正如@lephitar 在拒绝编辑中指出的那样,您的枚举是错误的:case One 应该从 1 开始,而不是 0。位掩码使用 2 和 2^0=1、2^ 的幂1=2 等
        【解决方案8】:

        为 Swift 2/3 更新

        从 swift 2 开始,添加了一个新的解决方案作为“原始选项集”(see: Documentation),这与我的原始响应基本相同,但使用允许任意值的结构。

        这是原来的问题改写为OptionSet

        struct MyOptions: OptionSet
        {
            let rawValue: UInt8
            
            static let One = MyOptions(rawValue: 0x01)
            static let Two = MyOptions(rawValue: 0x02)
            static let Four = MyOptions(rawValue: 0x04)
            static let Eight = MyOptions(rawValue: 0x08)
        }
        
        let m1 : MyOptions = .One
        
        let combined : MyOptions = [MyOptions.One, MyOptions.Four]
        

        与新值的组合可以完全像Set 操作(因此是 OptionSet 部分),.union 一样完成:

        m1.union(.Four).rawValue // Produces 5
        

        与 C 等效项中的 One | Four 相同。至于One &amp; Mask != 0,可以指定为非空交集

        // Equivalent of A & B != 0
        if !m1.intersection(combined).isEmpty
        {
            // m1 belongs is in combined
        }
        

        奇怪的是,大多数 C 风格的按位枚举在 Swift 3 上都已转换为 OptionSet 等效项,但 Calendar.Compontents 取消了 Set&lt;Enum&gt;

        let compontentKeys : Set<Calendar.Component> = [.day, .month, .year]
        

        而原来的 NSCalendarUnit 是按位枚举。所以这两种方法都是可用的(因此原始响应仍然有效)

        原始回复

        我认为最好的办法是在 Swift 开发人员找到更好的方法之前避免使用位掩码语法。

        大多数情况下,可以使用enumSet 来解决问题

        enum Options
        {
            case A, B, C, D
        }
        
        var options = Set<Options>(arrayLiteral: .A, .D)
        

        一个和检查(options &amp; .A)可以定义为:

        options.contains(.A)
        

        或者对于多个“标志”可以是:

        options.isSupersetOf(Set<Options>(arrayLiteral: .A, .D))
        

        添加新标志 (options |= .C):

        options.insert(.C)
        

        这还允许使用枚举的所有新内容:自定义类型、与 switch case 的模式匹配等。

        当然,它没有按位运算的效率,也不兼容低级别的东西(比如发送蓝牙命令),但它对 UI 元素很有用,因为 UI 的开销超过了设置操作。

        【讨论】:

        • 作为 Swift 新手,当我尝试将“public”添加到上面为 Swift 2/3 显示的结构声明中时,我遇到了重大问题。当您为 Xcode 错误单击“Fix-it”时,情况只会变得更糟,这当然无济于事。终于在这里找到了一篇展示如何将结构公开的文章:swift-studies.com/blog/2015/6/17/…
        • @RenniePet 那么你的问题是什么?使结构的属性为public?
        • 没有。 (好吧,那也是,但是当它被标记为问题时,这很明显。)如果我没记错的话,我的问题是需要一个显式的 public init() 方法,但是错误消息(对我来说)很难理解,然后单击 Fix-it 执行其他操作,然后标记结构中的每一行。
        • 不编译。谢谢
        【解决方案9】:

        如果你想要 Swift 中的位域,那么枚举是错误的方式。最好这样做

        class MyBits {
            static let One =      0x01
            static let Two =      0x02
            static let Four =     0x04
            static let Eight =    0x08
        }
        let m1 = MyBits.One
        let combined = MyBits.One | MyBits.Four
        

        您实际上并不需要类/静态包装器,但我将其作为一种伪命名空间包含在内。

        【讨论】:

        • @Dis3buted 我的方式比枚举好,因为你不需要.rawValue
        【解决方案10】:

        使用原始值进行按位运算,然后使用结果创建一个新的枚举对象。

        let mask = UIViewAutoresizing(rawValue: UIViewAutoresizing.FlexibleWidth.rawValue|UIViewAutoresizing.FlexibleHeight.rawValue) self.view.autoresizingMask = mask

        【讨论】:

          【解决方案11】:

          这是我尝试制作一个在某种程度上类似于 C# 标志样式枚举的 Swift 枚举。但我只是在学习 Swift,所以这应该只被视为“概念证明”代码。

          /// This EnumBitFlags protocol can be applied to a Swift enum definition, providing a small amount
          /// of compatibility with the flags-style enums available in C#.
          ///
          /// The enum should be defined as based on UInt, and enum values should be defined that are powers
          /// of two (1, 2, 4, 8, ...). The value zero, if defined, should only be used to indicate a lack of
          /// data or an error situation.
          ///
          /// Note that with C# the enum may contain a value that does not correspond to the defined enum
          /// constants. This is not possible with Swift, it enforces that only valid values can be set.
          public protocol EnumBitFlags : RawRepresentable, BitwiseOperations {
          
             var rawValue : UInt { get }  // This provided automatically by enum
          
             static func createNew(_ rawValue : UInt) -> Self  // Must be defined as some boiler-plate code
          }
          
          /// Extension methods for enums that implement the EnumBitFlags protocol.
          public extension EnumBitFlags {
          
             // Implement protocol BitwiseOperations. But note that some of these operators, especially ~, 
             // will almost certainly result in an invalid (nil) enum object, resulting in a crash.
          
             public static func & (leftSide: Self, rightSide: Self) -> Self {
                return self.createNew(leftSide.rawValue & rightSide.rawValue)
             }
          
             public static func | (leftSide: Self, rightSide: Self) -> Self {
                return self.createNew(leftSide.rawValue | rightSide.rawValue)
             }
          
             public static func ^ (leftSide: Self, rightSide: Self) -> Self {
                return self.createNew(leftSide.rawValue ^ rightSide.rawValue)
             }
          
             public static prefix func ~ (x: Self) -> Self {
                return self.createNew(~x.rawValue)
             }
          
             public static var allZeros: Self {
                get {
                   return self.createNew(0)
                }
             }
          
             // Method hasFlag() for compatibility with C#
             func hasFlag<T : EnumBitFlags>(_ flagToTest : T) -> Bool {
                return (self.rawValue & flagToTest.rawValue) != 0
             }
          }
          

          这显示了如何使用它:

          class TestEnumBitFlags {
          
             // Flags-style enum specifying where to write the log messages
             public enum LogDestination : UInt, EnumBitFlags {
                case none = 0             // Error condition
                case systemOutput = 0b01  // Logging messages written to system output file
                case sdCard       = 0b10  // Logging messages written to SD card (or similar storage)
                case both         = 0b11  // Both of the above options
          
                // Implement EnumBitFlags protocol
                public static func createNew(_ rawValue : UInt) -> LogDestination {
                   return LogDestination(rawValue: rawValue)!
                }
             }
          
             private var _logDestination : LogDestination = .none
             private var _anotherEnum : LogDestination = .none
          
             func doTest() {
          
                _logDestination = .systemOutput
                assert(_logDestination.hasFlag(LogDestination.systemOutput))
                assert(!_logDestination.hasFlag(LogDestination.sdCard))
          
                _anotherEnum = _logDestination
                assert(_logDestination == _anotherEnum)
          
                _logDestination = .systemOutput | .sdCard
                assert(_logDestination.hasFlag(LogDestination.systemOutput) &&
                       _logDestination.hasFlag(LogDestination.sdCard))
          
                /* don't do this, it results in a crash
                _logDestination = _logDestination & ~.systemOutput
                assert(_logDestination == .sdCard)
                */
          
                _logDestination = .sdCard
                _logDestination |= .systemOutput
                assert(_logDestination == .both)
             }
          }
          

          欢迎提出改进建议。

          编辑:我自己已经放弃了这种技术,因此显然不能再推荐它了。

          最大的问题是 Swift 要求 rawValue 必须匹配定义的枚举值之一。如果只有 2 个或 3 个甚至 4 个标志位,这是可以的 - 只需定义所有组合值以使 Swift 满意。但是对于 5 个或更多标志位,它变得完全疯狂。

          我会留下这篇文章,以防有人发现它有用,或者作为警告不要这样做。

          我目前对这种情况的解决方案是基于使用结构而不是枚举,以及协议和一些扩展方法。这工作得更好。也许有一天我会在我更确定这不会适得其反时发布它。

          【讨论】:

          • 刚刚添加了一个枚举示例,在需要位域的地方遇到了问题,但没有找到解决方案。
          【解决方案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
          

          【讨论】:

            【解决方案13】:

            这对我有用。

            • 1
            • 1
            • 1
            • 1

                enum Collision: Int {
                  case Enemy, Projectile, Debris, Ground
                  func bitmask() -> UInt32 {
                      return 1 << self.rawValue
                    }
                }
              

            【讨论】:

            • 1 &lt;&lt; 0 = 0001
            【解决方案14】:

            任务

            flags_combination 获取所有flags。每个flagflags_combination 都是整数。 flags_combination = flag_1 | flags_2

            详情

            • Xcode 11.2.1 (11B500)、Swift 5.1

            解决方案

            import Foundation
            
            protocol FlagPrototype: CaseIterable, RawRepresentable where RawValue == Int {}
            extension FlagPrototype {
                init?(rawValue: Int) {
                    for flag in Self.allCases where flag.rawValue == rawValue {
                        self = flag
                        return
                    }
                    return nil
                }
                static func all(from combination: Int) -> [Self] {
                    return Self.allCases.filter { return combination | $0.rawValue == combination }
                }
            }
            

            用法

            enum Flag { case one, two, three }
            extension Flag: FlagPrototype {
                var rawValue: Int {
                    switch self {
                    case .one: return 0x1
                    case .two: return 0x2
                    case .three: return 0x4
                    }
                }
            }
            
            var flags = Flag.two.rawValue | Flag.three.rawValue
            let selectedFlags = Flag.all(from: flags)
            print(selectedFlags)
            if selectedFlags == [.two, .three] { print("two | three") }
            

            【讨论】:

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