Comment créer des énumérations de masque de bits de style NS_OPTIONS dans Swift?


137

Dans la documentation d'Apple sur l'interaction avec les API C, ils décrivent la manière NS_ENUMdont les énumérations de style C marquées sont importées en tant qu'énumérations Swift. Cela a du sens, et comme les énumérations dans Swift sont facilement fournies comme enumtype de valeur, il est facile de voir comment créer la nôtre.

Plus bas, il est dit ceci à propos des NS_OPTIONSoptions de style C marquées:

Swift importe également les options marquées de la NS_OPTIONSmacro. Alors que les options se comportent comme énumérations importées, les options peuvent également prendre en charge certaines opérations binaires, telles que &, |et ~. En Objective-C, vous représentez un jeu d'options vide avec la constante zéro ( 0). Dans Swift, utilisez nilpour représenter l'absence d'options.

Étant donné qu'il n'y a pas de optionstype valeur dans Swift, comment pouvons-nous créer une variable d'options de style C avec laquelle travailler?


3
Le très célèbre "NSHipster" de @ Mattt a une description détaillée du RawOptionsSetType: nshipster.com/rawoptionsettype
Klaas

Réponses:


258

Swift 3.0

Presque identique à Swift 2.0. OptionSetType a été renommé OptionSet et les énumérations sont écrites en minuscules par convention.

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

Au lieu de fournir une noneoption, la recommandation de Swift 3 est d'utiliser simplement un littéral de tableau vide:

let noOptions: MyOptions = []

Autre usage:

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

Dans Swift 2.0, les extensions de protocole prennent en charge la plupart des passe-partout pour ceux-ci, qui sont maintenant importés en tant que structure conforme à OptionSetType. ( RawOptionSetTypea disparu depuis Swift 2 beta 2.) La déclaration est beaucoup plus 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)
}

Nous pouvons maintenant utiliser la sémantique basée sur les ensembles avec 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

En regardant les options Objective-C qui ont été importés par Swift ( UIViewAutoresizingpar exemple), on peut voir que les options sont déclarées comme structconforme au protocole RawOptionSetType, qui est conforme à son tour _RawOptionSetType, Equatable, RawRepresentable, BitwiseOperationsType, et NilLiteralConvertible. Nous pouvons créer le nôtre comme ceci:

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

Nous pouvons maintenant traiter ce nouvel ensemble d'options, MyOptionscomme décrit dans la documentation d'Apple: vous pouvez utiliser la enumsyntaxe -like:

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

Et il se comporte également comme nous nous attendrions à ce que les options se comportent:

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

J'ai construit un générateur pour créer un ensemble d'options Swift sans toutes les recherches / remplacements.

Dernières: Modifications pour Swift 1.1 beta 3.


1
Cela n'a pas fonctionné pour moi à moins que j'aie fait valueun UInt32. Vous n'avez pas non plus besoin de définir aucune des fonctions, les fonctions pertinentes sont déjà définies pour RawOptionSets (par exemple func |<T : RawOptionSet>(a: T, b: T) -> T)
David Lawson

Merci, bon point sur les fonctions - je pense que le compilateur se plaignait de celles-ci alors que je n'avais pas le reste de la conformité du protocole en place. Quels problèmes avez-vous rencontrés UInt? Cela fonctionne bien pour moi.
Nate Cook

2
Existe-t-il une solution qui utilise enum au lieu de struct? J'ai besoin du mien pour être compatible avec objective-c ...
jowie

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

1
Dans ce cas, les documents d'Apple sont vraiment bons.
Mr Rogers le

12

Xcode 6.1 Beta 2 a apporté quelques modifications au RawOptionSetTypeprotocole (voir cette entrée de blog Airspeedvelocity et les notes de publication d'Apple ).

Basé sur l'exemple de Nate Cooks, voici une solution mise à jour. Vous pouvez définir votre propre jeu d'options comme ceci:

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

Il peut ensuite être utilisé comme ceci pour définir des variables:

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

Et comme ceci pour tester les 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")
}

8

Exemple Swift 2.0 de la documentation:

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

Vous pouvez le trouver ici


6

Dans Swift 2 (actuellement bêta dans le cadre de la version bêta de Xcode 7), les NS_OPTIONStypes -style sont importés en tant que sous-types du nouveau OptionSetTypetype. Et grâce à la nouvelle fonctionnalité d' extensions de protocole et à la manière dont OptionSetTypeest implémentée dans la bibliothèque standard, vous pouvez déclarer vos propres types qui s'étendent OptionsSetTypeet obtiennent toutes les mêmes fonctions et méthodes que les NS_OPTIONStypes -style importés .

Mais ces fonctions ne sont plus basées sur des opérateurs arithmétiques bit à bit. Le fait de travailler avec un ensemble d'options booléennes non exclusives en C nécessite de masquer et de modifier les bits dans un champ est un détail d'implémentation. Vraiment, un ensemble d'options est un ensemble ... une collection d'objets uniques. Ainsi OptionsSetTypeobtient toutes les méthodes du SetAlgebraTypeprotocole, comme la création à partir de la syntaxe littérale de tableau, les requêtes comme contains, le masquage avec intersection, etc. (Plus besoin de se rappeler quel caractère amusant utiliser pour quel test d'appartenance!)


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

Si vous n'avez pas besoin d'interagir avec Objective-C et que vous voulez simplement la sémantique de surface des masques de bits dans Swift, j'ai écrit une simple "bibliothèque" appelée BitwiseOptions qui peut le faire avec des énumérations Swift régulières, 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!")
}

etc. Aucun bit réel n'est retourné ici. Ce sont des opérations définies sur des valeurs opaques. Vous pouvez trouver l'essentiel ici .


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

Je travaille actuellement sur une version Swift 2.0 de ceci.
Gregory Higley

2

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 au OptionSetTypeprotocole, qui présente une interface de type set 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
    }
}

2

Si la seule fonctionnalité dont nous avons besoin est un moyen de combiner des options |et de 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éez une option protocolet une surcharge |et &:

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) 

2

Il suffit de publier un exemple supplémentaire pour quiconque se demande si vous pouvez combiner des options composées. Vous pouvez, et ils se combinent comme vous vous en doutez si vous êtes 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](au moins sémantiquement):

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

1

Personne d'autre ne l'a mentionné - et je me suis en quelque sorte trompé après quelques bricolages - mais un Swift Set semble fonctionner assez bien.

Si nous pensons (peut-être à un diagramme de Venn?) À ce qu'un masque de bits représente réellement, c'est un ensemble éventuellement vide.

Bien sûr, en abordant le problème à partir des premiers principes, nous perdons la commodité des opérateurs bit à bit, mais gagnons de puissantes méthodes basées sur des ensembles qui améliorent 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 parce que je pense que cela vient d'une approche basée sur les premiers principes du problème - un peu comme Swift - plutôt que d'essayer d'adapter des solutions de type C.

Je voudrais également entendre quelques cas d'utilisation d'Obj-C qui remettraient en question ce paradigme différent, où les valeurs brutes entières montrent encore du mérite.


1

Afin d'éviter de coder en dur les positions de bits, ce qui est inévitable lors de l' utilisation (1 << 0), (1 << 1), (1 << 15)etc. , ou pire encore 1, 2, 16384etc. , ou une variante hexadécimal, on pourrait d' abord définit les bits dans un enum, puis laissez dit ENUM faire le calcul ordinale 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)
}

Juste un exemple ajouté où vous n'avez rien à coder en dur.
Peter Ahlberg

1

J'utilise ce qui suit, j'ai besoin des deux valeurs que je peux obtenir, rawValue pour l'indexation des tableaux et la valeur pour les indicateurs.

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

Et si vous en avez besoin de plus, ajoutez simplement une propriété calculée.

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

re: Création de bacs à sable et de signets à l'aide de jeux d'options avec plusieurs options

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

solution au besoin de combiner des options pour les créations, utile lorsque toutes les options ne s'excluent pas mutuellement.


0

La réponse de Nate est bonne mais je ferais du bricolage, comme ceci:

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

Utiliser un type de jeu d'options, en utilisation Swift 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
Ceci est plus ou moins déjà couvert dans cette réponse .
Pang
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.