type the raw not have enum does cannot enums swift bit-fields

enums - the - Declarar y usar un enum de campo de bits en Swift



enum swift 4 (13)

¿Cómo deberían declararse y usarse los campos de bits en Swift?

Declarar una enumeración como esta sí funciona, pero al intentar juntar los valores de OR 2 no se puede compilar:

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

Observé cómo Swift importa los tipos de enum de Foundation, y lo hace definiendo una struct que se ajuste al protocolo RawOptionSet :

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

Y el protocolo RawOptionSet es:

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

Sin embargo, no hay documentación sobre este protocolo y no puedo encontrar la forma de implementarlo. Además, no está claro si esta es la forma oficial de Swift de implementar campos de bits o si esto es solo cómo lo representa el puente Objective-C.


Actualizado para Swift 2/3

Desde swift 2, se ha agregado una nueva solución como "conjunto de opciones sin procesar" ( ver: Documentación ), que es esencialmente la misma que mi respuesta original, pero usando estructuras que permiten valores arbitrarios.

Esta es la pregunta original reescrita como un 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]

La combinación con nuevos valores se puede hacer exactamente como las operaciones Set (por lo tanto, la parte del conjunto de opciones), .union , del mismo modo:

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

Lo mismo que haciendo One | Four One | Four en su equivalente C En cuanto a One & Mask != 0 , se puede especificar como una intersección no vacía

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

Curiosamente, la mayoría de las enumeraciones bit a bit del estilo C se han convertido a su equivalente OptionSet en Swift 3, pero Calendar.Compontents elimina un Set<Enum> :

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

Mientras que la NSCalendarUnit original era una enumeración bit a bit. Entonces ambos enfoques son utilizables (por lo tanto, la respuesta original sigue siendo válida)

Respuesta original

Creo que lo mejor que se puede hacer es simplemente evitar la sintaxis de la máscara de bits hasta que los desarrolladores de Swift encuentren una forma mejor.

La mayoría de las veces, el problema se puede resolver usando una enum un Set

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

An y check ( options & .A ) podrían definirse como:

options.contains(.A)

O para múltiples "banderas" podría ser:

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

Agregar nuevos indicadores ( options |= .C ):

options.insert(.C)

Esto también permite usar todo lo nuevo con enumeraciones: tipos personalizados, coincidencia de patrones con la caja del interruptor, etc.

Por supuesto, no tiene la eficiencia de las operaciones bit a bit, ni sería compatible con cosas de bajo nivel (como enviar comandos bluetooth), pero es útil para los elementos de UI que la sobrecarga de la UI supere el costo de las operaciones de conjunto.


Aquí hay algo que armé para intentar hacer una enumeración de Swift que se asemeje en cierta medida a una enumeración de estilo de banderas C #. Pero estoy aprendiendo Swift, por lo que esto solo debe considerarse un código de "prueba de concepto".

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

Esto muestra cómo se puede usar:

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

Sugerencias para mejorar son bienvenidas.

EDITAR: He renunciado a esta técnica yo mismo, y por lo tanto, obviamente no puedo recomendarlo más.

El gran problema es que Swift exige que rawValue debe coincidir con uno de los valores enum definidos. Esto está bien si solo hay 2 o 3 o incluso 4 bits de indicador: simplemente defina todos los valores de combinación para que Swift sea feliz. Pero para 5 o más bits de bandera se vuelve totalmente loco.

Dejaré esto publicado en caso de que alguien lo encuentre útil, o tal vez como una advertencia de cómo NO hacerlo.

Mi solución actual a esta situación se basa en el uso de una estructura en lugar de enum, junto con un protocolo y algunos métodos de extensión. Esto funciona mucho mejor Tal vez lo publique algún día cuando esté más seguro de que eso no es bueno tampoco va a ser contraproducente para mí.


Creo que tal vez algunas de las respuestas aquí están desactualizadas con soluciones demasiado complicadas. Esto funciona bien para mi..

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 }


El famoso "NSHipster" de @Mattt tiene una extensa descripción detallada de RawOptionsSetType : http://nshipster.com/rawoptionsettype/

Incluye un práctico Xcode recortado:

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


Esto funcionó para mí.

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


Haga una operación bit a bit usando el valor bruto y luego cree un nuevo objeto enum usando el resultado.

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


Mostraron cómo hacer esto en uno de los videos de WWDC.

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

Tenga en cuenta que combined será el tipo Int y en realidad obtendrá un error de compilación si especifica let combined: MyEnum . Eso es porque no hay ningún valor enum para 0x05 que es el resultado de la expresión.


Puede construir una struct que se ajuste al protocolo RawOptionSet , y podrá usarlo como el tipo de enum incorporado, pero también con la funcionalidad de máscara de bits. La respuesta aquí muestra cómo: Enumeraciones de máscara de bits de estilo NS_OPTIONS de Swift .


Si no necesita interoperar con Objective-C y solo quiere la sintaxis de las máscaras de bits en Swift, he escrito una "biblioteca" simple llamada BitwiseOptions que puede hacer esto con enumeraciones Swift regulares, por ejemplo:

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

y así. No se están volteando bits reales aquí. Estas son operaciones establecidas en valores opacos. Puedes encontrar la esencia here .


Si quieres bitfield en Swift, entonces enum es el camino equivocado. Es mejor que hagas esto

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

Realmente no necesita el contenedor de clase / estática, pero lo incluyo como un tipo de pseudo espacio de nombres.


Supongo que algo así es cómo están modelando las opciones de enumeración en 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") }

... donde myOptions y nextOptions son del tipo TestOptions - No estoy exactamente seguro de cómo se supone que actúen fromMask() y getLogicValue() aquí (solo hice algunas conjeturas), tal vez alguien podría entender esto y resolverlo ?


Tienes que usar .toRaw () después de cada miembro:

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

trabajará. Porque tal como está, solo intentas asignar "Uno" que es un tipo de MyEnum, no un número entero. Como dice la documentación de Apple :

"A diferencia de C y Objective-C, a los miembros de la enumeración Swift no se les asigna un valor entero predeterminado cuando se crean. En el ejemplo de CompassPoints, Norte, Sur, Este y Oeste no son implícitamente iguales a 0, 1, 2 y 3. En cambio, los diferentes miembros de enumeración son valores totalmente desarrollados por derecho propio, con un tipo explícitamente definido de CompassPoint ".

por lo tanto, debe usar valores sin formato si desea que los miembros representen otro tipo, como se describe a here :

Los miembros de enumeración pueden venir prepoblados con valores predeterminados (llamados valores sin formato), que son todos del mismo tipo. El valor bruto para un miembro de enumeración particular siempre es el mismo. Los valores sin procesar pueden ser cadenas, caracteres o cualquiera de los tipos de números enteros o de coma flotante. Cada valor sin formato debe ser único dentro de su declaración de enumeración. Cuando los enteros se usan para valores brutos, se incrementan automáticamente si no se especifica ningún valor para algunos de los miembros de la enumeración. Acceda al valor bruto de un miembro de enumeración con su método toRaw.


Uso lo siguiente Necesito los dos valores que puedo obtener, rawValue para indexar matrices y valor para 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