Wie erstelle ich Bitmask-Aufzählungen im NS_OPTIONS-Stil in Swift?

137

In der Dokumentation von Apple zur Interaktion mit C-APIs wird beschrieben, wie NS_ENUMmarkierte C-Aufzählungen als Swift-Aufzählungen importiert werden. Dies ist sinnvoll, und da Aufzählungen in Swift leicht als Werttyp bereitgestellt enumwerden können, ist es leicht zu erkennen, wie wir unsere eigenen erstellen.

Weiter unten steht NS_OPTIONSFolgendes über markierte Optionen im C-Stil:

Swift importiert auch Optionen, die mit dem NS_OPTIONSMakro markiert sind . Während der Optionen für importierte Aufzählungen ähnlich verhalten, können die Optionen auch einige bitweise Operationen, wie zum Beispiel unterstützen &, |und ~. In Objective-C stellen Sie einen leeren Optionssatz mit der Konstanten Null ( 0) dar. Verwenden Sie nilin Swift, um das Fehlen von Optionen darzustellen.

optionsWie können wir eine C-Style-Optionsvariable erstellen, mit der wir arbeiten können , da es in Swift keinen Wertetyp gibt ?

Nate Cook
quelle
3
@ Mattts sehr berühmter "NSHipster" hat eine ausführliche Beschreibung des RawOptionsSetType: nshipster.com/rawoptionsettype
Klaas

Antworten:

258

Swift 3.0

Fast identisch mit Swift 2.0. OptionSetType wurde in OptionSet umbenannt und Aufzählungen werden gemäß 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 noneOption bereitzustellen, empfiehlt Swift 3, einfach ein leeres Array-Literal zu verwenden:

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 größten Teil der Boilerplate für diese, die jetzt als konforme Struktur importiert werden OptionSetType. ( RawOptionSetTypeist ab 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 die satzbasierte Semantik verwenden mit 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

Mit Blick auf den Objective-C - Optionen , die von Swift importiert wurden ( UIViewAutoresizingzum Beispiel), können wir sehen , dass die Optionen deklarieren sind als structdass Konform Protokoll RawOptionSetType, was wiederum Konform _RawOptionSetType, Equatable, RawRepresentable, BitwiseOperationsType, und NilLiteralConvertible. 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 diesen neuen Optionssatz MyOptionswie in der Apple-Dokumentation beschrieben behandeln: Sie können eine 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 erwarten, dass sich Optionen verhalten:

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 ein Swift-Optionsset ohne das Suchen / Ersetzen zu erstellen .

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

Nate Cook
quelle
1
Es hat bei mir nicht funktioniert, es sei denn, ich habe valueeine gemacht UInt32. Sie müssen auch keine der Funktionen definieren, relevante Funktionen sind bereits für RawOptionSets (z. B. func |<T : RawOptionSet>(a: T, b: T) -> T) definiert
David Lawson
Vielen Dank, großartiger Punkt zu den Funktionen - ich glaube, der Compiler hat sich über diese beschwert, als ich den Rest der Protokollkonformität nicht hatte. Mit welchen Problemen haben Sie gesehen UInt? Es funktioniert gut für mich.
Nate Cook
2
Gibt es eine Lösung, die Enum anstelle von Struct verwendet? Ich brauche meine, um mit Objective-C kompatibel zu sein ...
Jowie
1
@ Jowieenum CollisionTypes: UInt32 { case Player = 1 case Wall = 2 case Star = 4 case Vortex = 8 case Finish = 16 }
McCoyLBI
1
In diesem Fall sind Apples Dokumente wirklich gut.
Herr Rogers
12

Xcode 6.1 Beta 2 brachte einige Änderungen am RawOptionSetTypeProtokoll (siehe diesen Airspeedvelocity-Blogeintrag und die Apple-Versionshinweise ).

Basierend auf dem Beispiel von Nate Cooks ist hier eine aktualisierte Lösung. Sie können Ihren eigenen Optionssatz 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 folgendermaßen verwendet werden, um Variablen zu definieren:

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

Und so auf Bits testen:

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")
}
Klaas
quelle
8

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

Tomasz Bąk
quelle
6

In Swift 2 (derzeit Beta als Teil der Xcode 7-Beta) werden NS_OPTIONSTypstile als Untertypen des neuen OptionSetTypeTyps importiert . Dank der neuen Protokollerweiterungsfunktion und der Art und Weise, OptionSetTypewie sie in der Standardbibliothek implementiert ist, können Sie Ihre eigenen Typen deklarieren OptionsSetType, die dieselben Funktionen und Methoden wie importierte NS_OPTIONSTypen erweitern und 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 Twiddeln von Bits in einem Feld. Dies ist ein Implementierungsdetail. Wirklich, eine Reihe von Optionen ist eine Reihe ... eine Sammlung einzigartiger Gegenstände. So werden OptionsSetTypealle Methoden aus dem SetAlgebraTypeProtokoll abgerufen, z. B. Erstellung aus Array-Literal-Syntax, Abfragen wie contains, Maskieren mit intersectionusw. (Sie müssen sich nicht mehr daran erinnern, welches lustige Zeichen für welchen Mitgliedschaftstest verwendet werden soll!)

Rickster
quelle
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`
}
PhuocLuong
quelle
4

Wenn Sie nicht mit Objective-C zusammenarbeiten müssen und nur die Oberflächensemantik von Bitmasken in Swift möchten , habe ich eine einfache "Bibliothek" namens BitwiseOptions geschrieben, die dies mit regulären Swift-Aufzählungen 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 tatsächlichen Bits umgedreht. Dies sind festgelegte Operationen für undurchsichtige Werte. Das Wesentliche finden Sie hier .

Gregory Higley
quelle
@ChrisPrince Das liegt höchstwahrscheinlich daran, dass es für Swift 1.0 erstellt wurde und seitdem nicht mehr aktualisiert wurde.
Gregory Higley
Ich arbeite gerade an einer Swift 2.0-Version davon.
Gregory Higley
2

Wie Rickster bereits erwähnt hat, können Sie OptionSetType in Swift 2.0 verwenden. NS_OPTIONS-Typen werden als konform mit dem OptionSetTypeProtokoll importiert , das eine satzartige 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 Arbeitsweise:

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
    }
}
Antoine
quelle
2

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 &einer Alternative zu Nate Cooks Antwort enthalten, könnte dies sein:

Erstellen Sie eine Option protocolund Ü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 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) 
Simple99
quelle
2

Veröffentlichen Sie einfach ein zusätzliches Beispiel für alle anderen, die sich gefragt haben, ob Sie zusammengesetzte Optionen kombinieren können. Sie können und sie kombinieren sich wie erwartet, 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 glättet das Set [.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)"
Jarrod Smith
quelle
1

Niemand sonst hat es erwähnt - und ich habe es nach einigem Basteln irgendwie falsch gemacht -, aber ein Swift-Set scheint ziemlich gut zu funktionieren.

Wenn wir (vielleicht nach einem Venn-Diagramm?) Überlegen, was eine Bitmaske tatsächlich darstellt, handelt es sich um eine möglicherweise leere Menge.

Wenn wir uns dem Problem anhand der ersten Prinzipien nähern, verlieren wir natürlich die Bequemlichkeit bitweiser Operatoren, erhalten jedoch leistungsstarke 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, dass es sich um einen ersten Ansatz für das Problem handelt - ähnlich wie bei Swift - und nicht darum, Lösungen im C-Stil anzupassen.

Ich würde auch gerne einige Obj-C-Anwendungsfälle hören, die dieses andere Paradigma in Frage stellen würden, bei dem die ganzzahligen Rohwerte immer noch Verdienst zeigen.

Insektenspray
quelle
1

Um eine harte Codierung der Bitpositionen zu vermeiden, die bei der Verwendung unvermeidbar ist (1 << 0),(1 << 1) , (1 << 15)usw. oder noch schlimmer 1, 2, 16384usw. oder eine hexadezimalen Variation könnte man definiert zunächst die Bits in ein enum, dann sagte lassen Enumeration das Bit Ordnungs Berechnung tun:

// 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)
}
SwiftArchitect
quelle
Gerade ein Beispiel hinzugefügt, in dem Sie nichts hart codieren müssen.
Peter Ahlberg
1

Ich verwende Folgendes: Ich benötige beide Werte, die ich erhalten kann: rawValue zum Indizieren von Arrays und value 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 benötigen, 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"
        }
    }
}
Peter Ahlberg
quelle
1

Betreff: 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 Notwendigkeit, Optionen für Kreationen zu kombinieren, nützlich, wenn sich nicht alle Optionen gegenseitig ausschließen.

Slashlos
quelle
0

Nates Antwort ist gut, aber ich würde es so 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)
}
Ethan
quelle
0

Verwenden Sie einen Optionstyp, der in Swift 3 verwendet wird 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]
}
geek1706
quelle
1
Dies wird in dieser Antwort mehr oder weniger bereits behandelt .
Pang