140 votes

Comment créer des énumérations bitmask de type NS_OPTIONS en Swift ?

Dans la documentation d'Apple sur l'interaction avec les API en C, ils décrivent la manière suivante NS_ENUM -Les énumérations de style C marquées sont importées comme des énumérations Swift. C'est logique, et puisque les énumérations en Swift sont facilement fournies par la fonction enum il est facile de voir comment créer notre propre type de valeur.

Plus bas, il est dit ceci à propos de NS_OPTIONS -Options de style C marquées :

Swift importe également les options marquées du symbole NS_OPTIONS macro. Alors que options se comportent de manière similaire aux énumérations importées, les options peuvent aussi supporter certaines opérations binaires, telles que & , | y ~ . En Objective-C, on représente un ensemble d'options vide par la constante zéro ( 0 ). Dans Swift, utilisez nil pour représenter l'absence de toute option.

Étant donné qu'il n'y a pas de options en Swift, comment créer une variable d'options de type C pour travailler avec ?

3 votes

Le très célèbre "NSHipster" de @Mattt contient une description détaillée de l'appareil. RawOptionsSetType : nshipster.com/rawoptionsettype

0 votes

4voto

Gregory Higley Points 4509

Si vous n'avez pas besoin d'interopérer avec l'Objective-C et que vous voulez simplement l'option sémantique de surface de masques de bits en Swift, j'ai écrit une simple "bibliothèque" appelée BitwiseOptions qui peut faire cela avec des énumérations Swift ordinaires, par exemple :

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

et ainsi de suite. Aucun bit réel n'est inversé ici. Ce sont des opérations sur des valeurs opaques. Vous pouvez trouver le gist aquí .

0 votes

@ChrisPrince C'est probablement parce qu'il a été créé pour Swift 1.0 et n'a pas été mis à jour depuis.

0 votes

Je suis en train de travailler sur une version Swift 2.0 de ceci.

2voto

Twan Points 265

Comme Rickster l'a déjà mentionné, vous pouvez utiliser OptionSetType dans Swift 2.0. Les types NS_OPTIONS sont importés comme étant conformes à la norme OptionSetType qui présente une interface de type ensemble pour les options :

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

Cela vous donne cette façon de travailler :

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

2voto

Jarrod Smith Points 1392

Je poste juste un exemple supplémentaire pour tous ceux qui se demandaient si vous pouviez combiner des options composées. C'est le cas, et elles se combinent comme on peut s'y attendre si on est habitué aux bons vieux champs de bits :

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

Il aplatit l'ensemble [.AB, .X] en [.A, .B, .X] (du moins sur le plan sémantique) :

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

2voto

Simple99 Points 335

Si la seule fonctionnalité dont nous avons besoin est un moyen de combiner les options avec les éléments suivants | et vérifier si les options combinées contiennent une option particulière avec & une alternative à la réponse de Nate Cook pourrait être la suivante :

Créer une option protocol et la surcharge | y & :

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

Maintenant nous pouvons créer des structures d'options plus simplement comme ceci :

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

Ils peuvent être utilisés comme suit :

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

myMethod(options: .One | .Three)

1voto

BugSpray Points 113

Personne d'autre ne l'a mentionné - et je l'ai découvert par hasard après quelques bricolages - mais un ensemble Swift semble fonctionner assez bien.

Si nous réfléchissons (peut-être à l'aide d'un diagramme de Venn ?) à ce qu'un masque binaire représente réellement, il s'agit d'un ensemble éventuellement vide.

Bien sûr, en abordant le problème à partir des premiers principes, nous perdons la commodité des opérateurs binaires, mais nous gagnons de puissantes méthodes basées sur les ensembles, ce qui améliore la lisibilité.

Voici mon bricolage par exemple :

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

Je trouve cela agréable car j'ai l'impression qu'il s'agit d'une approche du problème basée sur les premiers principes - un peu comme Swift - plutôt que d'essayer d'adapter des solutions de style C.

J'aimerais également entendre des cas d'utilisation d'Obj-C qui remettent en question ce paradigme différent, où les valeurs brutes entières ont encore du mérite.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X