Jak tworzyć wyliczenia bitmask w stylu NS OPTIONS w języku Swift?

W dokumentacji Apple na temat interakcji z interfejsami API C, opisują sposób, w jaki NS_ENUM-oznaczone w stylu C wyliczenia są importowane jako wyliczenia Swift. Ma to sens, a ponieważ wyliczenia w języku Swift są łatwo dostarczane jako typ wartości enum, łatwo jest zobaczyć, jak stworzyć własne.

Dalej, mówi to o NS_OPTIONS-zaznaczonych opcjach w stylu C:

Swift importuje również opcje oznaczone makrem NS_OPTIONS. A także mając na uwadze opcje zachowują się podobnie do zaimportowanych wyliczenia, opcje mogą również obsługa niektórych operacji bitowych, takich jak &, |, i ~. W Objective-C, reprezentujesz pusty zestaw opcji ze stałą zero (0). W Swift, użyj nil, aby przedstawić brak jakichkolwiek opcji.

Biorąc pod uwagę, że w języku Swift nie istnieje typ wartości options, Jak możemy utworzyć zmienną opcji w stylu C do pracy?

Author: jtbandes, 2014-06-05

15 answers

Swift 3.0

Prawie identyczny z Swift 2.0. OptionSetType został przemianowany na OptionSet, a liczby są pisane małymi literami według konwencji.

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)
}
W przeciwieństwie do innych formatów, nie można ich używać do tworzenia tablic z pustą tablicą.]}
let noOptions: MyOptions = []

Inne użycie:

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

W Swift 2.0, rozszerzenia protokołu zajmują się większością kotła dla nich, które są teraz importowane jako struktura zgodna z OptionSetType. (RawOptionSetType zniknął z Swift 2 beta 2.) Deklaracja jest o wiele prostsza:

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

Teraz możemy używać semantyki opartej na zestawach z 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

Patrząc na Opcje Objective-C, które zostały zaimportowane przez Swift (UIViewAutoresizing, na przykład), widzimy, że opcje są zadeklarowane jako struct, która jest zgodna z protokołem RawOptionSetType, który z kolei jest zgodny z _RawOptionSetType, Equatable, RawRepresentable, BitwiseOperationsType, i NilLiteralConvertible. Możemy tworzyć własne w ten sposób:

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

Teraz możemy leczyć to nowy zestaw opcji, MyOptions, tak jak opisano w dokumentacji Apple: możesz użyć enum - podobnej składni:

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

I zachowuje się tak, jak byśmy oczekiwali, że opcje będą się zachowywać:

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

Zbudowałem generator, aby utworzyć zestaw opcji Swift BEZ wszystkich funkcji find/Replace.

Najnowsze: modyfikacje dla Swift 1.1 beta 3.

 238
Author: Nate Cook,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-06 04:21:31

Xcode 6.1 Beta 2 przyniósł pewne zmiany do protokołu RawOptionSetType (Zobacz ten Airspeedvelocity wpis blog iApple release notes ).

Na podstawie przykładu Nate Cooks tutaj jest zaktualizowane rozwiązanie. Możesz zdefiniować swój własny zestaw opcji w następujący sposób:

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

Może być następnie użyty w ten sposób do zdefiniowania zmiennych:

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

I tak do testowania bitów:

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
Author: Klaas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-30 21:14:04

Swift 2.0 przykład z dokumentacji:

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

Możesz go znaleźć tutaj

 8
Author: Tomasz BÄ…k,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-28 11:01:22

W Swift 2 (obecnie beta jako część Xcode 7 beta), typy NS_OPTIONS-style są importowane jako podtypy nowego OptionSetType typ. A dzięki nowej funkcji Protocol Extensions i sposobowi implementacji OptionSetType w bibliotece standardowej, możesz zadeklarować własne typy, które rozszerzają OptionsSetType i uzyskać wszystkie te same funkcje i metody, które importowane typy NS_OPTIONS-style get.

Ale te funkcje nie są już oparte na bitowych operatorach arytmetycznych. Że praca z zestaw niewyłącznych opcji logicznych w C wymaga maskowania, a przesuwanie bitów w polu jest szczegółem implementacji. Tak naprawdę zestaw opcji to zestaw ... kolekcja unikalnych przedmiotów. Więc OptionsSetType pobiera wszystkie metody z SetAlgebraType protokół, jak tworzenie ze składni tablicy, zapytania typu contains, maskowanie za pomocą intersection, itd. (Nie musisz już pamiętać, która zabawna postać użyć dla którego testu członkostwa!)

 6
Author: rickster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-08 23:01:06
//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
Author: PhuocLuong,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-15 04:33:01

Jeśli nie potrzebujesz współdziałać z Objective - C i po prostu chcesz semantyki powierzchniowej masek bitowych w języku Swift, napisałem prostą "bibliotekę" o nazwie BitwiseOptions, która może to zrobić z regularnymi wyliczeniami Swift, np.:

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

I tak dalej. Nie ma tu żadnych prawdziwych bitów. Są to operacje ustawione na nieprzezroczystych wartościach. Możesz znaleźć gist tutaj .

 3
Author: Gregory Higley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-28 18:37:08

Jeśli jedyną funkcją, której potrzebujemy, jest sposób łączenia opcji z | i sprawdzanie, czy połączone opcje zawierają konkretną opcję z & alternatywą dla odpowiedzi Nate ' a Cooka może być taka:

Tworzenie opcji protocol i przeciążenie | i &:

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

Teraz możemy tworzyć struktury opcji w prostszy sposób:

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

Mogą być używane w następujący sposób:

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

myMethod(options: .One | .Three) 
 2
Author: Simple99,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-16 12:20:16

Jak już wspomniał Rickster, możesz użyć opcji OptionSetType W Swift 2.0. Typy NS_OPTIONS są importowane jako zgodne z protokołem OptionSetType, który prezentuje interfejs podobny do zestawu dla opcji:

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

Daje Ci taki sposób pracy:

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
Author: Antoine,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-10 18:33:16

Po prostu zamieszczam dodatkowy przykład dla każdego, kto zastanawiał się, czy możesz połączyć opcje złożone. Można, i łączą się tak, jak można się spodziewać, jeśli jesteś przyzwyczajony do starych dobrych bitfieldów: {]}

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

Spłaszcza zbiór [.AB, .X] do [.A, .B, .X] (przynajmniej semantycznie):

print(state)      // 0b111 as expected: "State(rawValue: 7)"
print(State.AB)   // 0b11 as expected: "State(rawValue: 3)"
 1
Author: Jarrod Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-02 03:38:46

Nikt inny o tym nie wspomniał - i trochę się na to pomyliłem po jakimś majsterkowaniu - ale Szybki zestaw wydaje się działać dość dobrze.

Jeśli pomyślimy (może do diagramu Venna?) o tym, co faktycznie reprezentuje maska bitowa, jest to prawdopodobnie pusty zestaw.

Oczywiście, podchodząc do problemu z pierwszych zasad, tracimy wygodę operatorów bitowych, ale zyskujemy potężne metody oparte na zestawach, które poprawiają czytelność.

Oto moje Majsterkowanie dla przykład:

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

Uważam to za miłe, ponieważ czuję, że wynika to z pierwszego podejścia do problemu-podobnie jak Swift-zamiast próbować dostosować rozwiązania w stylu C.

Chciałbym również usłyszeć kilka przypadków użycia Obj-C, które podważyłyby ten inny paradygmat, gdzie wartości całkowite raw nadal wykazują wartość merytoryczną.

 1
Author: BugSpray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-02 11:42:15

W celu uniknięcia twardego kodowania pozycji bitowych, co jest nieuniknione przy użyciu (1 << 0), (1 << 1), (1 << 15) itd. lub nawet gorzej 1, 2, 16384 itd. albo jakaś zmienna szesnastkowa, można najpierw zdefiniować bity w enum, a następnie niech wspomniane enum wykona obliczenia porządkowe bitów:

// 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
Author: SwiftArchitect,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-19 04:02:22

Używam następujących wartości potrzebuję obu, które mogę uzyskać, rawValue dla indeksowania tablic i wartość dla FLAG.

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

A jeśli ktoś potrzebuje więcej, wystarczy dodać właściwość obliczeniową.

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
Author: Dis3buted,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-23 19:36:49

Re: Tworzenie piaskownicy i zakładek z wykorzystaniem zestawów opcji z kilkoma opcjami

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

RozwiÄ…zanie potrzeby Å‚Ä…czenia opcji dla kreacji, przydatne, gdy nie wszystkie opcje wykluczajÄ… siÄ™ wzajemnie.

 1
Author: slashlos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-04 17:56:14

Odpowiedź Nate ' a jest dobra, ale zrobiłbym to DIY, tak:

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
Author: Ethan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:03:05

Użyj typu Option Set, w swift 3 Użyj 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
Author: geek1706,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-06 02:56:38