webentwicklung-frage-antwort-db.com.de

Wie erstellt man Bitmasken-Enumerationen im NS_OPTIONS-Stil in Swift?

In der Apple-Dokumentation zur Interaktion mit C-APIs wird beschrieben, wie NS_ENUM- markierte Aufzählungen im C-Stil als Swift-Aufzählungen importiert werden. Dies ist sinnvoll, und da Aufzählungen in Swift schnell als Werttyp enum bereitgestellt werden, ist es leicht zu sehen, wie man eigene erstellt.

Weiter unten heißt es über NS_OPTIONS- markierte Optionen im C-Stil:

Swift importiert auch Optionen, die mit dem Makro NS_OPTIONS markiert sind. Wohingegen Optionen verhalten sich ähnlich wie importierte Aufzählungen, Optionen können auch Unterstützung einiger bitweiser Operationen wie &, | und ~. In Objective-C Sie repräsentieren einen leeren Optionssatz mit der Konstanten Null (0). Im Swift, verwenden Sie nil, um das Fehlen von Optionen darzustellen.

Da es in Swift keinen Werttyp options gibt, wie können wir eine C-Style-Optionsvariable erstellen, mit der Sie arbeiten können?

120
Nate Cook

Swift 3.0

Fast identisch mit Swift 2.0. OptionSetType wurde in OptionSet umbenannt und die Aufzählungen werden nach Konvention in Kleinbuchstaben geschrieben.

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

Anstatt eine none-Option bereitzustellen, wird in Swift 3 einfach ein leeres Array-Literal verwendet:

let noOptions: MyOptions = []

Andere Verwendung:

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 kümmern sich Protokollerweiterungen um den Großteil der Boilerplate, die jetzt als eine Struktur importiert werden, die OptionSetType entspricht. (RawOptionSetType ist seit Swift 2 Beta 2 verschwunden.) Die Deklaration ist viel einfacher:

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

Jetzt können wir set-basierte Semantik mit MyOptions verwenden:

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

Schnell 1.2

Wenn Sie die Objective-C-Optionen betrachten, die von Swift importiert wurden (zum Beispiel UIViewAutoresizing), können Sie feststellen, dass Optionen als struct deklariert werden, die dem Protokoll RawOptionSetType entspricht, das wiederum _RawOptionSetType, Equatable, RawRepresentable, BitwiseOperationsType und NilLiteralConvertible entspricht. . Wir können unsere eigenen so erstellen:

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

Jetzt können wir diese neue Optionsgruppe MyOptions genauso behandeln, wie in der Dokumentation von Apple beschrieben: Sie können enum- ähnliche Syntax verwenden:

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

Und es verhält sich auch so, als würden wir Optionen erwarten:

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

Ich habe einen Generator gebaut, um einen Swift-Optionssatz zu erstellen ohne all das Suchen/Ersetzen. 

Latest: Änderungen für Swift 1.1 Beta 3.

243
Nate Cook

Xcode 6.1 Beta 2 hat einige Änderungen am RawOptionSetType-Protokoll vorgenommen (siehe diesen Airspeedvelocity-Blogeintrag und die Apple-Versionshinweise ).

Basierend auf Nate Cooks Beispiel ist hier eine aktualisierte Lösung. Sie können Ihr eigenes Optionsset folgendermaßen definieren:

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

Es kann dann so verwendet werden, um Variablen zu definieren:

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

Und so zum Testen auf 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-Beispiel aus der Dokumentation:

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

Sie finden es hier

8
Tomasz Bąk

In Swift 2 (derzeit Beta als Teil der Xcode 7-Betaversion) werden NS_OPTIONS- Stil-Typen als Untertypen des neuen Typs OptionSetType importiert. Und dank der neuen Protocol Extensions -Funktion und der Art und Weise, wie OptionSetType in der Standardbibliothek implementiert ist, können Sie Ihre eigenen Typen deklarieren, die OptionsSetType erweitern, und alle Funktionen und Methoden erhalten, die importierte NS_OPTIONS- Stiltypen erhalten.

Diese Funktionen basieren jedoch nicht mehr auf bitweisen arithmetischen Operatoren. Das Arbeiten mit einer Reihe nicht exklusiver boolescher Optionen in C erfordert das Maskieren und Verwischen von Bits in einem Feld ist ein Implementierungsdetail. Wirklich ist eine Menge von Optionen eine Menge ... eine Sammlung von einzigartigen Elementen. So bekommt OptionsSetType alle Methoden aus dem SetAlgebraType Protokoll, wie die Erstellung aus der Array-Literal-Syntax, Abfragen wie contains, Maskieren mit intersection usw. (Sie müssen sich nicht mehr daran erinnern, welcher lustige Charakter für welchen Mitgliedschaftstest verwendet werden muss!).

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

Wenn Sie nicht mit Objective-C zusammenarbeiten müssen und nur die surface-Semantik von Bitmasken in Swift benötigen, habe ich eine einfache "Bibliothek" namens BitwiseOptions geschrieben, die dies mit regulären Swift-Enumerationen tun kann, z.

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

und so weiter. Hier werden keine echten Bits umgedreht. Dies sind Operationen, die auf undurchsichtige Werte eingestellt sind. Sie finden den Gist hier .

4
Gregory Higley

Wenn die einzige Funktionalität, die wir benötigen, eine Möglichkeit ist, Optionen mit | zu kombinieren und zu prüfen, ob kombinierte Optionen eine bestimmte Option mit & enthalten, könnte dies eine Alternative zu Nate Cooks Antwort sein:

Erstellen Sie eine Option protocol und eine Überladung | und &:

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

Jetzt können wir Optionsstrukturen einfacher so erstellen:

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

Sie können wie folgt verwendet werden:

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

myMethod(options: .One | .Three) 
2
Simple99

Posten Sie einfach ein zusätzliches Beispiel für alle, die sich gefragt haben, ob Sie zusammengesetzte Optionen kombinieren können. Sie können und sie kombinieren, wie Sie es erwarten würden, wenn Sie an gute alte Bitfelder gewöhnt sind:

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

Es reduziert die Menge [.AB, .X] in [.A, .B, .X] (zumindest semantisch):

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

Wie Rickster bereits erwähnt hat, können Sie OptionSetType in Swift 2.0 verwenden. NS_OPTIONS-Typen werden importiert, da sie dem OptionSetType-Protokoll entsprechen, das eine satzähnliche Schnittstelle für Optionen bietet:

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

Es gibt Ihnen diese Art zu arbeiten:

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: Sandbox- und Lesezeichenerstellung mit Optionssätzen mit mehreren Optionen

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

lösung für die Kombination von Optionen für Kreationen, nützlich, wenn sich nicht alle Optionen gegenseitig ausschließen.

1
slashlos

Um eine harte Kodierung der Bitpositionen zu vermeiden, die bei der Verwendung von (1 << 0), (1 << 1), (1 << 15) usw. oder noch schlechter 1, 2, 16384 usw. oder einer hexadezimalen Variation unvermeidbar ist, könnte man zuerst die Bits in einer enum definieren und diese dann angeben enum tun die bit ordinal berechnung:

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

Niemand sonst hat es erwähnt - und nach einigem Basteln bin ich irgendwie darauf gestoßen -, aber ein Swift-Set scheint ziemlich gut zu funktionieren.

Wenn wir (vielleicht an ein Venn-Diagramm?) Darüber nachdenken, was eine Bitmaske tatsächlich darstellt, ist es eine möglicherweise leere Menge.

Wenn wir uns dem Problem von den ersten Prinzipien aus nähern, verlieren wir natürlich die Bequemlichkeit bitweiser Operatoren, gewinnen aber leistungsfähige satzbasierte Methoden, die die Lesbarkeit verbessern.

Hier ist mein Basteln zum Beispiel:

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

Ich finde das schön, weil ich der Meinung bin, es komme von einem ersten Prinzipansatz des Problems - ähnlich wie von Swift -, anstatt zu versuchen, C-artige Lösungen anzupassen.

Ich möchte auch gerne einige Anwendungsfälle von Obj-C hören, die dieses andere Paradigma in Frage stellen, bei dem die ganzzahligen Rohwerte immer noch Vorteile aufweisen. 

1
BugSpray

Ich verwende Folgendes: Ich brauche die beiden Werte, die ich erhalten kann, rawValue für die Indexierung von Arrays und den Wert für 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

Und wenn Sie mehr brauchen, fügen Sie einfach eine berechnete Eigenschaft hinzu.

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

Verwenden Sie einen Optionssatztyp, in 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]
}
0
geek1706

Nates Antwort ist gut, aber ich würde es gerne selbst machen:

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