在Swift中声明并使用位字段枚举

问题描述 投票:35回答:13

如何在Swift中声明和使用位字段?

声明这样的枚举确实有效,但尝试将OR 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枚举类型,并通过定义符合struct协议的RawOptionSet来实现:

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桥接器如何表示它们。

enums swift bit-fields
13个回答
26
投票

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


1
投票

我使用以下内容我需要我可以得到的两个值,rawValue用于索引数组和value for flags。

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

0
投票

使用原始值进行按位操作,然后使用结果创建新的枚举对象。

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


0
投票

这是我放在一起尝试制作一个类似于C#flags-style枚举的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个或更多的标志位,它变得非常疯狂。

如果有人发现它有用,我会留下这个,或者作为不怎么做的警告。

我目前解决这种情况的方法是基于使用struct而不是enum,以及协议和一些扩展方法。这样做效果更好。也许有一天我会发布它,当我更确定那不是也不会适得其反。


0
投票

这对我有用。

  • 1 << 0 //0000
  • 1 << 1 //0010
  • 1 << 2 //0100
  • << 1 3 // 1000 enum Collision: Int { case Enemy, Projectile, Debris, Ground func bitmask() -> UInt32 { return 1 << self.rawValue } }

13
投票

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

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

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


12
投票

更新了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-equivalent中做One | Four相同。至于One & Mask != 0,可以指定为非空交集

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

奇怪的是,大多数C风格的按位枚举已经转换为他们在Swift 3上的OptionSet等价物,但Calendar.Compontents消除了Set<Enum>

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

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

原始回应

我认为最好的办法是简单地避免使用bitmask语法,直到Swift开发人员找到更好的方法。

大多数时候,使用enumSet可以解决问题

enum Options
{
    case A, B, C, D
}

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

An和check(options & .A)可以定义为:

options.contains(.A)

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

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

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

options.insert(.C)

这也允许使用枚举的所有新东西:自定义类型,与开关盒的模式匹配等。

当然,它没有按位操作的效率,也不兼容低级别的东西(比如发送蓝牙命令),但它对UI元素的用处是UI的开销超过了Set操作的成本。


11
投票

我想也许这里的一些答案已经过时了,过于复杂的解决方案?这对我来说很好..

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
}

7
投票

如果您不需要与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


3
投票

@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) }
    // ...
}

2
投票

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

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

将工作。因为它只是在尝试分配“One”,它是MyEnum类型,而不是整数。正如Apple's documentation所说:

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

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

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


1
投票

我猜这样的事情是他们在Foundation中为enum选项建模的方式:

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()应该如何在这里采取行动(我只是做了一些最好的猜测),也许有人可以选择这个并解决它?


1
投票

如果你想在Swift中使用bitfield,那么枚举是错误的。更好的就是这样做

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

你真的不需要类/静态包装器,但我把它作为一种伪命名空间包含在内。

© www.soinside.com 2019 - 2024. All rights reserved.