Como criar enumerações de máscaras de bits no estilo NS_OPTIONS no Swift?

137

Na documentação da Apple sobre interação com APIs C, eles descrevem como as NS_ENUMenumerações no estilo C marcadas como são importadas como enumerações Swift. Isso faz sentido, e como as enumerações no Swift são prontamente fornecidas como o enumtipo de valor, é fácil ver como criar as nossas.

Mais abaixo, diz o seguinte sobre as NS_OPTIONSopções de estilo C marcadas:

Swift também importa opções marcadas com a NS_OPTIONSmacro. Considerando que as opções se comportam de forma semelhante a enumerações importados, as opções também podem suportar algumas operações bit a bit, tais como &, |, e ~. No Objective-C, você representa uma opção vazia definida com a constante zero ( 0). No Swift, use nilpara representar a ausência de quaisquer opções.

Dado que não há um optionstipo de valor no Swift, como podemos criar uma variável de opções C-Style para trabalhar?

Nate Cook
fonte
3
@ Do Mattt muito famoso "NSHipster" tem uma extensa descrição do RawOptionsSetType: nshipster.com/rawoptionsettype
Klaas
Possível duplicado de Declarando e usando uma enum campo de bits na Swift
Peter Ahlberg

Respostas:

258

Swift 3.0

Quase idêntico ao Swift 2.0. OptionSetType foi renomeado para OptionSet e as enumerações são escritas em minúsculas por convenção.

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

Em vez de fornecer uma noneopção, a recomendação do Swift 3 é simplesmente usar um literal de matriz vazio:

let noOptions: MyOptions = []

Outro uso:

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

No Swift 2.0, as extensões de protocolo cuidam da maior parte do padrão, agora importadas como uma estrutura em conformidade OptionSetType. ( RawOptionSetTypedesapareceu a partir do Swift 2 beta 2.) A declaração é muito mais simples:

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

Agora podemos usar semântica baseada em conjunto com 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

Olhando para as opções de Objective-C que foram importados por Swift ( UIViewAutoresizing, por exemplo), podemos ver que opções são declarados como um structem conformidade com o protocolo RawOptionSetType, que por conforma por sua vez a _RawOptionSetType, Equatable, RawRepresentable, BitwiseOperationsType, e NilLiteralConvertible. Podemos criar nossos próprios assim:

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

Agora podemos tratar esse novo conjunto de opções MyOptions, exatamente como descrito na documentação da Apple: você pode usar a enumsintaxe-like:

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

E também se comporta como esperamos que as opções se comportem:

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

Eu construí um gerador para criar um conjunto de opções Swift sem toda a localização / substituição.

Mais recentes: Modificações para o Swift 1.1 beta 3.

Nate Cook
fonte
1
Não funcionou para mim, a menos que eu fiz valueum UInt32. Você também não precisa definir qualquer uma das funções, funções relevantes já estão definidos para RawOptionSets (por exemplo func |<T : RawOptionSet>(a: T, b: T) -> T)
David Lawson
Obrigado, ótimo argumento sobre as funções - acho que o compilador estava reclamando delas quando não havia o restante da conformidade do protocolo em vigor. Com quais problemas você viu UInt? Está funcionando bem para mim.
Nate Cook
2
Existe uma solução que usa enum em vez de struct? Eu preciso meu para ser compatível com o objectivo de c ...
jowie
1
@jowieenum CollisionTypes: UInt32 { case Player = 1 case Wall = 2 case Star = 4 case Vortex = 8 case Finish = 16 }
mccoyLBI
1
Nesse caso, os documentos da Apple são realmente bons.
Rogers
12

O Xcode 6.1 Beta 2 trouxe algumas alterações ao RawOptionSetTypeprotocolo (consulte esta entrada do blog Airspeedvelocity e as notas de versão da Apple ).

Baseado no exemplo de Nate Cooks, aqui está uma solução atualizada. Você pode definir seu próprio conjunto de opções assim:

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

Em seguida, pode ser usado assim para definir variáveis:

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

E assim para testar os 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")
}
Klaas
fonte
8

Exemplo do Swift 2.0 da documentação:

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

Você pode encontrá-lo aqui

Tomasz Bąk
fonte
6

No Swift 2 (atualmente beta como parte do Xcode 7 beta), os NS_OPTIONStipos de estilo são importados como subtipos do novo OptionSetTypetipo. E, graças ao novo recurso Extensões de Protocolo e à maneira como OptionSetTypeé implementada na biblioteca padrão, você pode declarar seus próprios tipos que estendem OptionsSetTypee obtêm todas as mesmas funções e métodos que os NS_OPTIONStipos de estilo importados obtêm.

Mas essas funções não são mais baseadas em operadores aritméticos bit a bit. Que trabalhar com um conjunto de opções booleanas não exclusivas em C requer mascarar e girar bits em um campo é um detalhe da implementação. Realmente, um conjunto de opções é um conjunto ... uma coleção de itens exclusivos. Então, OptionsSetTypeobtém todos os métodos do SetAlgebraTypeprotocolo, como criação da sintaxe literal da matriz, consultas como contains, mascaramento com intersectionetc. (Não é mais necessário lembrar qual personagem engraçado usar para qual teste de associação!)

rickster
fonte
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
fonte
4

Se você não precisa interoperar com o Objective-C e apenas deseja a semântica de superfície das máscaras de bits no Swift, escrevi uma "biblioteca" simples chamada BitwiseOptions que pode fazer isso com enumerações regulares do Swift, por exemplo:

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

e assim por diante. Nenhum bit real está sendo invertido aqui. Essas são operações definidas em valores opacos. Você pode encontrar a essência aqui .

Gregory Higley
fonte
@ChrisPrince Provavelmente é porque foi criado para o Swift 1.0 e não foi atualizado desde então.
Gregory Higley
Na verdade, estou trabalhando em uma versão Swift 2.0 disso.
Gregory Higley
2

Como Rickster já mencionou, você pode usar OptionSetType no Swift 2.0. Os tipos NS_OPTIONS são importados conforme o OptionSetTypeprotocolo, que apresenta uma interface de conjunto para opções:

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

Dá a você esta maneira de trabalhar:

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
fonte
2

Se a única funcionalidade de que precisamos é uma maneira de combinar opções |e verificar se as opções combinadas contêm uma opção específica com &uma alternativa à resposta de Nate Cook, pode ser:

Crie opções protocole sobrecarga |e &:

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

Agora podemos criar estruturas de opções de maneira mais simples:

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

Eles podem ser usados ​​da seguinte maneira:

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

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

Basta postar um exemplo extra para qualquer pessoa que estivesse se perguntando se você poderia combinar opções compostas. Você pode, e eles combinam como você esperaria se estivesse acostumado a bons e antigos campos 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

Nivela o conjunto [.AB, .X]em [.A, .B, .X](pelo menos semanticamente):

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

Ninguém mais o mencionou - e eu meio que errei depois de alguns ajustes - mas um Swift Set parece funcionar bastante bem.

Se pensarmos (talvez em um diagrama de Venn?) Sobre o que uma máscara de bit está realmente representando, é um conjunto possivelmente vazio.

Obviamente, ao abordar o problema a partir dos primeiros princípios, perdemos a conveniência dos operadores bit a bit, mas obtemos poderosos métodos baseados em conjuntos que melhoram a legibilidade.

Aqui está o meu conserto, por exemplo:

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

Acho isso legal porque sinto que ele vem de uma abordagem de primeiros princípios para o problema - bem como Swift -, em vez de tentar adaptar soluções no estilo C.

Também gostaria de ouvir alguns casos de uso de Obj-C que desafiariam esse paradigma diferente, onde os valores brutos inteiros ainda mostram mérito.

BugSpray
fonte
1

A fim de evitar o disco de codificação as posições de bit, que é inevitável quando se utiliza (1 << 0), (1 << 1), (1 << 15)etc., ou ainda pior 1, 2, 16384etc., ou alguma variação hexadecimal, pode-se primeiro define os bits em uma enum, em seguida, deixar o referido enum fazer o cálculo ordinal 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)
}
SwiftArchitect
fonte
Acabei de adicionar um exemplo em que você não precisa codificar nada.
Peter Ahlberg
1

Eu uso o seguinte e preciso dos dois valores que posso obter, rawValue para indexar matrizes e valor para sinalizadores.

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

E se precisar de mais, basta adicionar uma propriedade computada.

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
fonte
1

re: Sandbox e criações de favoritos usando conjuntos de opções com várias opções

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

solução para a necessidade de combinar opções de criações, útil quando nem todas as opções são mutuamente exclusivas.

slashlos
fonte
0

A resposta de Nate é boa, mas eu faria DIY, assim:

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
fonte
0

Use um tipo de conjunto de opções, em um uso rápido 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]
}
geek1706
fonte
1
Isso já está mais ou menos coberto nesta resposta .
Pang