web-dev-qa-db-fra.com

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

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

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

Swift importe également les options marquées avec la macro NS_OPTIONS. Tandis que les options se comportent de la même manière que les énumérations importées, les options peuvent aussi prend en charge certaines opérations au niveau des bits, telles que &, | et ~. En Objective-C, vous représentez un jeu d'options vide avec la constante zéro (0). Dans Swift, utilisez nil pour représenter l'absence de toute option.

Étant donné qu’il n’ya pas de type de valeur options dans Swift, comment pouvons-nous créer une variable d’options C-Style avec laquelle travailler?

120
Nate Cook

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 option none, la recommandation de Swift 3 consiste simplement à utiliser un littéral de tableau vide:

let noOptions: MyOptions = []

Autre utilisation:

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 s’occupent de la plupart des règles d’application, qui sont maintenant importées en tant que structure conforme à OptionSetType. (RawOptionSetType a 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 examinant les options Objective-C importées par Swift (UIViewAutoresizing, par exemple), nous pouvons voir que les options sont déclarées en tant que struct conforme au protocole RawOptionSetType, lequel est lui aussi conforme à _RawOptionSetType, Equatable, RawRepresentable, BitwiseOperationsType et NilLiteralConvertible . Nous pouvons créer notre propre 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, MyOptions, comme décrit dans la documentation d'Apple: vous pouvez utiliser la syntaxe enum- like:

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

Et il se comporte également comme nous nous attendions à 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 jeu d'options Swift } sans toute la recherche/remplacement. 

Dernières: Modifications pour Swift 1.1 beta 3.

243
Nate Cook

Xcode 6.1 bêta 2 a apporté quelques modifications au protocole RawOptionSetType (voir cette entrée de blog Airspeedvelocity et les notes de publication de 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 ça pour tester des 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")
}
11
Klaas

Swift 2.0 exemple 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

8
Tomasz Bąk

Dans Swift 2 (version bêta actuellement intégrée à la version bêta de Xcode 7), les types de style NS_OPTIONS- sont importés en tant que sous-types du nouveau type OptionSetType . Et grâce à la nouvelle fonctionnalité Protocol Extensions et à la façon dont OptionSetType est implémenté dans la bibliothèque standard, vous pouvez déclarer vos propres types qui étendent OptionsSetType et obtenir toutes les mêmes fonctions et méthodes que celles importées par les types de style NS_OPTIONS-.

Mais ces fonctions ne sont plus basées sur des opérateurs arithmétiques au niveau des bits. Le fait de travailler avec un ensemble d’options booléennes non exclusives en C nécessite le masquage et le twiddling de bits dans un champ est un détail d’implémentation. Vraiment, un ensemble d’options est un set ... un ensemble d’éléments uniques. Donc, OptionsSetType obtient toutes les méthodes du protocole SetAlgebraType , comme la création à partir de la syntaxe littérale d'un tableau, des requêtes telles que contains, le masquage avec intersection, etc.

6
rickster
//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`
}
5
PhuocLuong

Si vous n'avez pas besoin d'interagir avec Objective-C et que vous voulez juste 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 le Gist ici .

4
Gregory Higley

Si la seule fonctionnalité dont nous avons besoin est un moyen de combiner des options avec | 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:

Créez une option protocol et 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 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
Simple99

Il suffit de poster 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 avez l'habitude des 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)"
2
Jarrod Smith

Comme Rickster l'a déjà mentionné, vous pouvez utiliser OptionSetType dans Swift 2.0. Les types NS_OPTIONS sont importés conformément au protocole 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
    }
}
1
Antoine

re: créations Sandbox et signets en utilisant des jeux d'options avec plusieurs options

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

solution pour avoir besoin de combiner les options pour les créations, utile lorsque toutes les options ne sont pas mutuellement exclusives.

1
slashlos

Personne d'autre n'en a parlé - et j'ai un peu gaffé après quelques travaux de bricolage - 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 au niveau des bits, mais nous obtenons 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 j’ai le sentiment que cela découle d’une approche fondée sur les principes de base, semblable à celle de Swift, plutôt que d’essayer d’adapter des solutions de type C.

Voudrait aussi entendre certains cas d’utilisation Obj-C qui remettraient en cause ce paradigme différent, où les valeurs entières brutes sont toujours valables. 

1
BugSpray

J'utilise ce qui suit, j'ai besoin des deux valeurs que je peux obtenir, rawValue pour les tableaux d'indexation et 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 l’on a besoin de plus, il suffit d’ajouter 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
Peter Ahlberg

Utilisez un type de jeu d'options, dans Swift 3, utilisez 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]
}
0
geek1706

La réponse de Nate est bonne mais je le ferais bricolage

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
Ethan