Swift'te NS_OPTIONS tarzı bitmask numaralandırmaları nasıl oluşturulur?


137

Apple'ın C API'leriyle etkileşim ile ilgili belgelerinde, yol NS_ENUMişaretli C stili numaralandırmaların Swift numaralandırmaları olarak içe aktarıldığını açıklar . Bu mantıklıdır ve Swift'teki numaralandırmalar enumdeğer türü olarak kolayca sağlandığından, kendimizi nasıl oluşturacağımızı görmek kolaydır.

Dahası, NS_OPTIONSişaretli C stili seçenekleri hakkında şunları söylüyor :

Swift ayrıca NS_OPTIONSmakro ile işaretlenmiş seçenekleri de içe aktarır . Seçenekler ithal numaralandırma benzer şekilde davranan Oysa seçenekleri de gibi bazı bitsel işlemleri, destekleyebilir &, |ve ~. Objective-C'de, sıfır ( 0) ile boş bir seçenek kümesini temsil edersiniz . Swift'te, nilherhangi bir seçeneğin yokluğunu göstermek için kullanın .

optionsSwift'te bir değer türü olmadığı göz önüne alındığında, çalışmak için bir C Stili seçenekleri değişkeni nasıl oluşturabiliriz?


3
@ Mattt'in çok ünlü "NSHipster" filminin kapsamlı bir açıklaması var RawOptionsSetType: nshipster.com/rawoptionsettype
Klaas

Yanıtlar:


258

Swift 3.0

Swift 2.0 ile neredeyse aynı. OptionSetType seçeneği OptionSet olarak yeniden adlandırıldı ve numaralandırmalar kural ile küçük harfle yazılır.

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

Bir noneseçenek sunmak yerine , Swift 3 önerisi boş bir dizi değişmezi kullanmaktır:

let noOptions: MyOptions = []

Diğer kullanım:

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

Swift 2.0'da, protokol uzantıları, bunlar için şimdi uygun bir yapı olarak içe aktarılan levha plakasının çoğunu halleder OptionSetType. ( RawOptionSetTypeSwift 2 beta 2'den itibaren kayboldu.) Beyan çok daha basit:

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

Şimdi set tabanlı semantiği şu şekilde kullanabiliriz 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")
}

Hızlı 1.2

(Swift tarafından ithal edildi Objective-C seçenekleri baktığımızda UIViewAutoresizingörneğin), biz seçenekleri olarak beyan görebiliriz structo protokole uygun olup RawOptionSetType, dönüş uygun olup te _RawOptionSetType, Equatable, RawRepresentable, BitwiseOperationsType, ve NilLiteralConvertible. Kendimizi böyle yaratabiliriz:

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

Şimdi bu yeni seçenek setini, MyOptionstıpkı Apple'ın belgelerinde açıklandığı gibi ele alabiliriz enum: benzeri sözdizimini kullanabilirsiniz :

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

Ayrıca, seçeneklerin davranmasını beklediğimiz gibi davranır:

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

Tüm bul / değiştir olmadan Swift seçenek kümesi oluşturmak için bir jeneratör oluşturdum .

En son: Swift 1.1 beta 3 için değişiklikler.


1
Yaptığım sürece benim için işe yaramadı valuebir UInt32. Ayrıca herhangi bir işlevi tanımlamanız gerekmez, ilgili işlevler zaten RawOptionSets (örn. func |<T : RawOptionSet>(a: T, b: T) -> T) İçin tanımlanmıştır
David Lawson

Teşekkürler, fonksiyonları hakkında büyük bir nokta - Ben derleyici yerinde protokol uyumluluk geri kalanı yoktu olanlar hakkında şikayet olduğunu düşünüyorum. Hangi sorunlarla karşılaştınız UInt? Benim için iyi çalışıyor.
Nate Cook

2
Struct yerine enum kullanan bir çözüm var mı? Ben
obj

1
@jowieenum CollisionTypes: UInt32 { case Player = 1 case Wall = 2 case Star = 4 case Vortex = 8 case Finish = 16 }
mccoyLBI

1
Bu durumda, Apple'ın belgeleri gerçekten iyidir.
Bay Rogers

12

Xcode 6.1 Beta 2, RawOptionSetTypeprotokolde bazı değişiklikler getirdi (bu Airspeedvelocity blog girişine ve Apple sürüm notlarına bakın ).

Nate Cooks örneğine dayanan burada güncellenmiş bir çözüm var. Kendi seçenek kümenizi şu şekilde tanımlayabilirsiniz:

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

Daha sonra değişkenleri tanımlamak için şu şekilde kullanılabilir:

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

Ve bitleri test etmek için böyle:

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

8

Belgelerden Swift 2.0 örneği:

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

Burada bulabilirsiniz


6

Swift 2'de (şu anda Xcode 7 beta'nın bir parçası olan beta), NS_OPTIONSstil türleri yeni OptionSetTypetürün alt türleri olarak içe aktarılır . Ve yeni Protokol Uzantıları özelliği ve OptionSetTypestandart kütüphanede uygulanma şekli sayesinde OptionsSetType, içe aktarılan NS_OPTIONSstil türleriyle aynı işlevleri ve yöntemleri genişleten ve elde eden kendi türlerinizi bildirebilirsiniz .

Ancak bu işlevler artık bitsel aritmetik işleçlere dayalı değildir. C'de bir dizi münhasır olmayan Boolean seçeneğiyle çalışmanın bir alanda maskeleme ve döndürme bitleri gerektirmesi bir uygulama detayıdır. Gerçekten, bir seçenek kümesi bir dizi ... benzersiz öğeleri koleksiyonu. Yani , dizi değişmez sözdiziminden oluşturma gibi sorgulama , maskeleme , vb gibi protokolleri OptionsSetTypetüm yöntemleri alır (Artık hangi üyelik testi için hangi komik karakteri kullanmak hatırlamak zorunda!)SetAlgebraTypecontainsintersection


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

4

Objective-C ile birlikte çalışmanız gerekmiyorsa ve Swift'teki bit maskelerinin yüzey semantiğini istiyorsanız, bunu düzenli Swift numaralandırmalarıyla yapabilen BitwiseOptions adlı basit bir "kütüphane" yazdım, örneğin:

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

ve bunun gibi. Burada gerçek bitler çevrilmiyor. Bunlar opak değerler üzerinde ayarlanmış işlemlerdir. Buradaki özü burada bulabilirsiniz .


@ChrisPrince Büyük olasılıkla Swift 1.0 için oluşturulduğu ve o zamandan beri güncellenmediği için.
Gregory Higley

Aslında bunun bir Swift 2.0 sürümü üzerinde çalışıyorum.
Gregory Higley

2

Rickster'in daha önce de belirtildiği gibi, Swift 2.0'da OptionSetType'ı kullanabilirsiniz . NS_OPTIONS türleri, OptionSetTypeprotokole uygun olarak içe aktarılır ve seçenekler için set benzeri bir arayüz sunar:

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

Size şu şekilde çalışma imkanı sağlar:

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

2

İhtiyacımız olan tek işlevsellik, seçenekleri birleştirmenin |ve kombine seçeneklerin &Nate Cook'un cevabına alternatif olan belirli bir seçenek içerip içermediğini kontrol etmenin bir yolu ise :

Bir seçenek oluşturma protocolve aşırı yük |ve &:

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

Şimdi seçenekler yapılarını daha basit bir şekilde oluşturabiliriz:

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

Bunlar aşağıdaki gibi kullanılabilir:

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

myMethod(options: .One | .Three) 

2

Bileşik seçenekleri birleştirip birleştiremeyeceğinizi merak eden herkes için ekstra bir örnek yayınlamanız yeterlidir. Eski bit alanlarına alışkın olup olmadığınızı beklediğiniz gibi birleştirebilirsiniz:

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

Bu set düzleştirir [.AB, .X]içine [.A, .B, .X](en azından semantik):

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

1

Başka hiç kimse bundan bahsetmedi - ve bir miktar müdahaleden sonra üzüldüm - ama bir Swift Seti oldukça iyi çalışıyor gibi görünüyor.

Bir bit maskesinin gerçekte neyi temsil ettiğini düşünürsek (belki bir Venn diyagramına?) Düşünürsek, muhtemelen boş bir kümedir.

Tabii ki, soruna ilk prensiplerden yaklaşırken, bitsel operatörlerin rahatlığını kaybediyoruz, ancak okunabilirliği artıran güçlü set tabanlı yöntemler elde ediyoruz.

İşte benim tinkering örneğin:

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

Bunu güzel buluyorum çünkü C tarzı çözümlere adapte olmak yerine soruna ilk prensip yaklaşımından (Swift gibi) geldiğini hissediyorum.

Ayrıca, tamsayı ham değerlerinin hala değer gösterdiği bu farklı paradigmaya meydan okuyacak bazı Obj-C kullanım örneklerini duymak isterim.


1

Kullanırken kaçınılmazdır bit konumlarını kodlayan sabit önlemek için, içinde (1 << 0), (1 << 1), (1 << 15)vb ya da daha kötüsü 1, 2, 16384vb ya da onaltılık varyasyon, bir birinci, bir biti tanımlar olabilir enum, numaralama bit sıra hesaplama yapmak daha sonra sözü geçen izin:

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

Sadece bir şey kodlamak zorunda değilsiniz örnek ekledi.
Peter Ahlberg

1

Ben alabilirim her iki değeri, dizinleri diziler için rawValue ve bayraklar için değer aşağıdaki kullanın.

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

Ve daha fazlasına ihtiyaç duyulursa, sadece hesaplanmış bir özellik ekleyin.

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

re: Çeşitli seçeneklere sahip seçenek kümelerini kullanarak sanal alan ve yer işareti oluşturma

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

Tüm seçenekler birbirini dışlamadığında yararlı olan, kreasyon seçeneklerini birleştirmeye ihtiyaç duyulan çözüm.


0

Nate'in yanıtı iyi ama ben DIY yapabilirdim, şöyle:

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

Bir Seçenek Kümesi Türü kullanın, hızlı 3 kullanımda 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]
}

1
Bu, bu cevaba az çok kapsanmaktadır .
Pang
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.