¿Cómo crear las enumeraciones de máscara de bits de estilo NS_OPTIONS en Swift?

En la documentación de Apple sobre la interacción con las API de C, describen la forma en que se NS_ENUM enumeraciones de estilo C con la NS_ENUM NS_ENUM como empadronamientos Swift. Esto tiene sentido, y como las enumeraciones en Swift se proporcionan fácilmente como el tipo de valor enum , es fácil ver cómo crear las nuestras.

Más abajo, dice esto sobre NS_OPTIONS -marked C-style options:

Swift también importa las opciones marcadas con la macro NS_OPTIONS . Mientras que las opciones se comportan de manera similar a las enumeraciones importadas, las opciones también pueden admitir algunas operaciones a nivel de bit, como & , | y ~ . En Objective-C, representa una opción vacía establecida con el cero constante ( 0 ). En Swift, use nil para representar la ausencia de cualquier opción.

Dado que no hay un tipo de valor de options en Swift, ¿cómo podemos crear una variable de opciones de estilo C para trabajar?

Swift 3.0

Casi idéntico a Swift 2.0. OptionSetType se renombró a OptionSet y las enumeraciones se escriben minúsculas por convención.

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

En lugar de proporcionar una opción none , la recomendación de Swift 3 es simplemente usar un literal vacío de matriz:

 let noOptions: MyOption = [] 

Otro uso:

 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

En Swift 2.0, las extensiones de protocolo se encargan de la mayoría de las repeticiones estándar para estos, que ahora se importan como una estructura que se ajusta a OptionSetType . ( RawOptionSetType ha desaparecido a partir de Swift 2 beta 2.) La statement es mucho más simple:

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

Ahora podemos usar la semántica basada en conjuntos con 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

Al observar las opciones de Objective-C que Swift ( UIViewAutoresizing , por ejemplo) importó, podemos ver que las opciones se declaran como una struct que se ajusta al protocolo RawOptionSetType , que a su vez se ajusta a _RawOptionSetType , Equatable , RawRepresentable , BitwiseOperationsType y NilLiteralConvertible . Podemos crear el nuestro así:

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

Ahora podemos tratar este nuevo conjunto de opciones, MyOptions , tal como se describe en la documentación de Apple: puede usar la syntax de tipo enum :

 let opt1 = MyOptions.FirstOption let opt2: MyOptions = .SecondOption let opt3 = MyOptions(4) 

Y también se comporta como esperamos que se comporten las opciones:

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

Construí un generador para crear un conjunto de opciones Swift sin todo el hallazgo / reemplazo.

Último: Modificaciones para Swift 1.1 beta 3.

Xcode 6.1 Beta 2 trajo algunos cambios al protocolo RawOptionSetType (vea esta entrada del blog Airspeedvelocity y las notas de la versión de Apple ).

Basado en el ejemplo de Nate Cooks aquí hay una solución actualizada. Puede definir su propia opción establecida de esta manera:

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

Se puede usar así para definir variables:

 let opt1 = MyOptions.FirstOption let opt2:MyOptions = .SecondOption let opt3 = MyOptions(4) 

Y así para probar 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") } 

Ejemplo de Swift 2.0 de la documentación:

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

Puedes encontrarlo aquí

En Swift 2 (actualmente beta como parte de Xcode 7 beta), los tipos de estilo NS_OPTIONS se importan como subtipos del nuevo tipo OptionSetType . Y gracias a la nueva característica Extensiones de protocolo y la forma en que se implementa OptionSetType en la biblioteca estándar, puede declarar sus propios tipos que amplían OptionsSetType y obtienen todas las mismas funciones y métodos que NS_OPTIONS tipos importados de estilo NS_OPTIONS .

Pero esas funciones ya no se basan en operadores aritméticos bit a bit. Que trabajar con un conjunto de opciones booleanas no exclusivas en C requiere enmascaramiento y bifurcar bits en un campo es un detalle de implementación. Realmente, un conjunto de opciones es un conjunto … una colección de artículos únicos. Así que OptionsSetType obtiene todos los métodos del protocolo SetAlgebraType , como la creación a partir de la syntax literal de la matriz, consultas como contains , enmascaramiento con intersection , etc. (¡Ya no tendrá que recordar qué personaje divertido usar para qué prueba de membresía!)

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

Si no necesita interoperar con Objective-C y solo desea la semántica de la superficie de las máscaras de bits en Swift, he escrito una simple “biblioteca” llamada BitwiseOptions que puede hacer esto con enumeraciones regulares de Swift, 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 aquí .

Si la única funcionalidad que necesitamos es una forma de combinar opciones con | y compruebe si las opciones combinadas contienen una opción particular con & una alternativa a la respuesta de Nate Cook podría ser esta:

Crear un protocol opciones y sobrecargar | y &

 protocol OptionsProtocol { var value: UInt { get } init (_ value: UInt) } func | (left: T, right: T) -> T { return T(left.value | right.value) } func & (left: T, right: T) -> Bool { if right.value == 0 { return left.value == 0 } else { return left.value & right.value == right.value } } 

Ahora podemos crear estructuras de opciones de forma más sencilla como sigue:

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

Se pueden usar de la siguiente manera:

 func myMethod(#options: MyOptions) { if options & .One { // Do something } } myMethod(options: .One | .Three) 

Como Rickster ya mencionó, puedes usar OptionSetType en Swift 2.0. Los tipos NS_OPTIONS se importan conforme al protocolo OptionSetType , que presenta una interfaz tipo conjunto para las opciones:

 struct CoffeeManipulators : OptionSetType { let rawValue: Int static let Milk = CoffeeManipulators(rawValue: 1) static let Sugar = CoffeeManipulators(rawValue: 2) static let MilkAndSugar = [Milk, Sugar] } 

Te da esta forma de trabajar:

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

Solo publicando un ejemplo extra para cualquier otra persona que se preguntaba si podría combinar opciones compuestas. Puedes, y se combinan como esperarías si estás acostumbrado a buenos campos de bit viejos:

 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 

Aplana el conjunto [.AB, .X] en [.A, .B, .X] (al menos semánticamente):

 print(state) // 0b111 as expected: "State(rawValue: 7)" print(State.AB) // 0b11 as expected: "State(rawValue: 3)" 

Nadie más lo ha mencionado, y yo cometí un error después de algunos retoques, pero un Swift Set parece funcionar bastante bien.

Si pensamos (¿quizás en un diagtwig de Venn?) Qué es lo que realmente representa una máscara de bits, es un conjunto posiblemente vacío.

Por supuesto, al abordar el problema desde los primeros principios, perdemos la comodidad de los operadores bit a bit, pero obtenemos métodos potentes basados ​​en conjuntos que mejoran la legibilidad.

Aquí está mi retoque, por ejemplo:

 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 = [.Tomato, .Salami, .Cheese, .Chicken, .Anchovies, .Beef] } func checkPizza(toppings: Set) { if toppings.contains(.Cheese) { print("Possible dairy allergies?") } let meats: Set = [.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]) 

Me parece agradable porque creo que proviene de un primer enfoque de principio al problema, al igual que Swift, en lugar de tratar de adaptar las soluciones de estilo C.

También me gustaría escuchar algunos casos de uso de Obj-C que desafiarían este paradigma diferente, donde los valores en bruto enteros aún muestran mérito.

Para evitar la encoding dura de las posiciones de bit, que es inevitable cuando se usan (1 << 0) , (1 << 1) , (1 << 15) etc. o incluso peor 1 , 2 , 16384 etc. o algunos hexadecimales variación, uno podría primero definir los bits en una enum , luego dejar que dicha enumeración haga el cálculo ordinal de bits:

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

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 

Y si uno necesita más simplemente agregue una propiedad calculada.

 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: Creaciones de sandbox y marcadores usando conjuntos de opciones con varias opciones

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

solución a la necesidad de combinar opciones para creaciones, útil cuando no todas las opciones son mutuamente excluyentes.

La respuesta de Nate es buena, pero yo la haría bricolaje, así:

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

Utilice un tipo de conjunto de opciones, en el uso rápido OptionSet 3

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