Come creare enumerazioni di maschere di bit in stile NS_OPTIONS in Swift?


137

Nella documentazione di Apple sull'interazione con le API C, descrivono il modo in NS_ENUMcui le enumerazioni in stile C marcate vengono importate come enumerazioni Swift. Ciò ha senso e poiché le enumerazioni in Swift sono prontamente fornite come enumtipo di valore, è facile vedere come crearne uno nostro.

Più in basso, dice questo sulle NS_OPTIONSopzioni in stile C contrassegnate:

Swift importa anche le opzioni contrassegnate con la NS_OPTIONSmacro. Mentre le opzioni si comportano in modo simile a enumerazioni importati, le opzioni possono anche sostenere alcune operazioni bit per bit, come ad esempio &, |e ~. In Objective-C, rappresenti un'opzione vuota impostata con la costante zero ( 0). In Swift, utilizzare nilper rappresentare l'assenza di eventuali opzioni.

Dato che non esiste un optionstipo di valore in Swift, come possiamo creare una variabile di opzioni C-Style con cui lavorare?


3
Il famosissimo "NSHipster" di Mattt ha una descrizione estesa di RawOptionsSetType: nshipster.com/rawoptionsettype
Klaas

Risposte:


258

Swift 3.0

Quasi identico a Swift 2.0. OptionSetType è stato rinominato in OptionSet e gli enum sono scritti in minuscolo per convenzione.

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

Invece di fornire noneun'opzione, la raccomandazione di Swift 3 è semplicemente usare un array letterale vuoto:

let noOptions: MyOptions = []

Altro utilizzo:

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

In Swift 2.0, le estensioni di protocollo si occupano della maggior parte della piastra di caldaia per queste, che ora sono importate come una struttura conforme OptionSetType. ( RawOptionSetTypeè scomparso a partire da Swift 2 beta 2.) La dichiarazione è molto più semplice:

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

Ora possiamo usare la semantica basata su set 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

Guardando le opzioni di Objective-C che sono stati importati da Swift ( UIViewAutoresizingper esempio), possiamo vedere che le opzioni sono dichiarate come una structche sia conforme al protocollo RawOptionSetType, che a sua volta conforme a _RawOptionSetType, Equatable, RawRepresentable, BitwiseOperationsType, e NilLiteralConvertible. Possiamo crearne uno nostro come questo:

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

Ora possiamo trattare questo nuovo set di opzioni MyOptions, proprio come descritto nella documentazione di Apple: puoi usare la enumsintassi simile a:

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

E si comporta anche come ci aspetteremmo che le opzioni si comportino:

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

Ho creato un generatore per creare un set di opzioni Swift senza tutte le operazioni di ricerca / sostituzione.

Ultime: modifiche per Swift 1.1 beta 3.


1
Non ha funzionato per me a meno che non ne abbia realizzato valueuno UInt32. Inoltre non è necessario definire nessuna delle funzioni, le funzioni rilevanti sono già definite per RawOptionSets (ad es. func |<T : RawOptionSet>(a: T, b: T) -> T)
David Lawson

Grazie, ottimo punto sulle funzioni: penso che il compilatore si stesse lamentando di quelle quando non avevo il resto della conformità del protocollo. Con quali problemi hai riscontrato UInt? Funziona bene per me.
Nate Cook,

2
Esiste una soluzione che utilizza enum invece di struct? Ho bisogno che il mio sia compatibile con
object

1
@jowieenum CollisionTypes: UInt32 { case Player = 1 case Wall = 2 case Star = 4 case Vortex = 8 case Finish = 16 }
mccoyLBI

1
In questo caso, i documenti di Apple sono davvero buoni.
Rogers,

12

Xcode 6.1 Beta 2 ha apportato alcune modifiche al RawOptionSetTypeprotocollo (vedi questo blog di Airspeedvelocity e le note di rilascio di Apple ).

Sulla base dell'esempio di Nate Cooks ecco una soluzione aggiornata. Puoi definire il tuo set di opzioni in questo modo:

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

Può quindi essere utilizzato in questo modo per definire le variabili:

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

E così per testare bit:

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

8

Esempio di Swift 2.0 dalla documentazione:

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

Puoi trovarlo qui


6

In Swift 2 (attualmente beta come parte dell'Xcode 7 beta), i NS_OPTIONStipi di stile vengono importati come sottotipi del nuovo OptionSetTypetipo. E grazie alla nuova funzionalità di estensione del protocollo e il modo in cui OptionSetTypeè implementato nella libreria standard, puoi dichiarare i tuoi tipi che si estendono OptionsSetTypee ottenere tutte le stesse funzioni e metodi che NS_OPTIONSottengono i tipi di stile importati .

Ma quelle funzioni non sono più basate su operatori aritmetici bit a bit. Il fatto di lavorare con una serie di opzioni booleane non esclusive in C richiede mascheramento e punte di bit in un campo è un dettaglio di implementazione. Davvero, un insieme di opzioni è un insieme ... una raccolta di oggetti unici. Quindi OptionsSetTypeottiene tutti i metodi dal SetAlgebraTypeprotocollo, come la creazione dalla sintassi letterale dell'array, query come contains, mascheramento intersection, ecc. (Non è più necessario ricordare quale personaggio divertente usare per quale test di appartenenza!)


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

4

Se non hai bisogno di interagire con Objective-C e vuoi solo la semantica di superficie delle maschere di bit in Swift, ho scritto una semplice "libreria" chiamata BitwiseOptions che può farlo con le normali enumerazioni Swift, ad esempio:

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

e così via. Nessun bit effettivo viene capovolto qui. Queste sono operazioni impostate su valori opachi. Puoi trovare l'essenza qui .


@ChrisPrince Molto probabilmente è perché è stato creato per Swift 1.0 e non è stato aggiornato da allora.
Gregory Higley,

Attualmente sto lavorando a una versione Swift 2.0 di questo.
Gregory Higley,

2

Come già menzionato Rickster, puoi usare OptionSetType in Swift 2.0. I tipi NS_OPTIONS vengono importati come conformi al OptionSetTypeprotocollo, che presenta un'interfaccia simile a un set per le opzioni:

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

Ti dà questo modo di lavorare:

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

2

Se l'unica funzionalità di cui abbiamo bisogno è un modo per combinare le opzioni |e verificare se le opzioni combinate contengono una particolare opzione con &un'alternativa alla risposta di Nate Cook potrebbe essere questa:

Crea un'opzione protocole un sovraccarico |e &:

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

Ora possiamo creare strutture di opzioni più semplicemente così:

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

Possono essere usati come segue:

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

myMethod(options: .One | .Three) 

2

Pubblicando solo un esempio in più per chiunque si chiedesse se fosse possibile combinare opzioni composte. Puoi, e si combinano come ti aspetteresti se sei abituato a buoni vecchi campi di bit:

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

Appiattisce il set [.AB, .X]in [.A, .B, .X](almeno semanticamente):

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

1

Nessun altro lo ha menzionato - e dopo averlo armeggiato mi sono quasi sbagliato - ma uno Swift Set sembra funzionare abbastanza bene.

Se pensiamo (forse a un diagramma di Venn?) Di ciò che in realtà rappresenta una maschera di bit, è forse un set vuoto.

Naturalmente, affrontando il problema dai primi principi, perdiamo la comodità degli operatori bit a bit, ma otteniamo potenti metodi basati su set che migliorano la leggibilità.

Ecco il mio armeggiare per esempio:

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

Lo trovo carino perché ritengo che derivi da un approccio ai primi principi al problema - proprio come Swift - piuttosto che cercare di adattare le soluzioni in stile C.

Vorrei anche ascoltare alcuni casi d'uso di Obj-C che sfiderebbero questo diverso paradigma, in cui i valori grezzi interi mostrano ancora merito.


1

Per evitare duro codificare le posizioni dei bit, che è inevitabile quando si usa (1 << 0), (1 << 1), (1 << 15)ecc o peggio ancora 1, 2, 16384ecc o qualche variazione esadecimale, si potrebbe prima definisce i bit in una enum, poi lasciare detto enum fare il calcolo ordinale bit:

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

Ho appena aggiunto un esempio in cui non è necessario codificare nulla.
Peter Ahlberg,

1

Uso quanto segue Ho bisogno di entrambi i valori che posso ottenere, rawValue per gli array di indicizzazione e valore per i flag.

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

E se uno ha bisogno di più, basta aggiungere una proprietà calcolata.

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

1

ri: Crea sandbox e segnalibri usando set di opzioni con diverse opzioni

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

soluzione alla necessità di combinare le opzioni per le creazioni, utile quando non tutte le opzioni si escludono a vicenda.


0

La risposta di Nate è buona, ma la farei fai-da-te, in questo modo:

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

0

Utilizzare un tipo di set di opzioni, in rapido uso 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]
}

1
Questo è più o meno già trattato in questa risposta .
Pang
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.