วิธีการสร้างการแจกแจง bitmask สไตล์ NS_OPTIONS ใน Swift


137

ในเอกสารประกอบของ Apple เกี่ยวกับการโต้ตอบกับ C APIs พวกเขาอธิบายถึงวิธีNS_ENUMการระบุการแจกแจงแบบ C ที่ทำเครื่องหมายด้วยวิธีเป็น Swift enumerations สิ่งนี้สมเหตุสมผลและเนื่องจากมีการระบุ enumerations ใน Swift เป็นenumประเภทค่าจึงง่ายต่อการดูวิธีการสร้างของเราเอง

นอกจากนี้ยังกล่าวถึงNS_OPTIONSตัวเลือก C-style ที่มีเครื่องหมาย:

Swift ยังนำเข้าตัวเลือกที่มีเครื่องหมายNS_OPTIONSแมโคร ในขณะที่ตัวเลือกลักษณะการทำงานคล้ายจะ enumerations นำเข้าตัวเลือกยังสามารถรองรับการดำเนินงานบางบิตเช่น&, และ| ~ใน Objective-C คุณหมายถึงชุดตัวเลือกที่ว่างเปล่าที่มีค่าคงที่เป็นศูนย์ ( 0) ใน Swift ใช้nilเพื่อแสดงการไม่มีตัวเลือกใด ๆ

ระบุว่าไม่มีoptionsประเภทค่าใน Swift เราจะสร้างตัวแปรตัวเลือก C-Style เพื่อทำงานได้อย่างไร


3
@ NSHipster ที่โด่งดังของ Mattt มีคำอธิบายที่กว้างขวางเกี่ยวกับRawOptionsSetType: nshipster.com/rawoptionsettype
Klaas

คำตอบ:


258

Swift 3.0

เกือบจะเหมือนกับ Swift 2.0 OptionSetType ถูกเปลี่ยนชื่อเป็น OptionSet และ enums เป็นตัวพิมพ์เล็กโดยการประชุม

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

แทนที่จะให้noneตัวเลือกคำแนะนำ Swift 3 คือใช้ตัวอักษรอาร์เรย์ที่ว่างเปล่า:

let noOptions: 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 2.0

ในสวิฟท์ 2.0 OptionSetTypeส่วนขยายโปรโตคอลดูแลส่วนใหญ่ของสำเร็จรูปสำหรับเหล่านี้ซึ่งจะนำเข้าในขณะนี้เป็นโครงสร้างที่สอดรับไป ( RawOptionSetTypeหายไปตั้งแต่ Swift 2 beta 2) การประกาศนั้นง่ายกว่ามาก:

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

ตอนนี้เราสามารถใช้ semantics แบบ set-based กับ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")
}

สวิฟท์ 1.2

มองไปที่ตัวเลือก Objective-C ที่ถูกนำเข้าโดยสวิฟท์ ( UIViewAutoresizingตัวอย่าง) เราจะเห็นว่าตัวเลือกมีการประกาศเป็นstructที่สอดคล้องกับโปรโตคอลRawOptionSetTypeซึ่งสอดหันไป _RawOptionSetType, Equatable, RawRepresentable, และBitwiseOperationsType NilLiteralConvertibleเราสามารถสร้างของเราเองเช่นนี้:

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

ตอนนี้เราสามารถจัดการชุดตัวเลือกใหม่นี้ได้MyOptionsเช่นเดียวกับที่อธิบายไว้ในเอกสารประกอบของ Apple: คุณสามารถใช้enumไวยากรณ์เหมือน

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

และมันก็ทำตัวเหมือนที่เราคาดหวังว่าตัวเลือกจะมีพฤติกรรม:

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

ฉันได้สร้างตัวสร้างขึ้นเพื่อสร้างชุดตัวเลือก Swiftโดยไม่ต้องค้นหา / แทนที่ทั้งหมด

ล่าสุด:การปรับเปลี่ยนสำหรับ Swift 1.1 beta 3


1
มันไม่ได้ทำงานสำหรับฉันถ้าฉันทำ นอกจากนี้คุณยังไม่จำเป็นต้องกำหนดใด ๆ ของฟังก์ชั่นการทำงานที่เกี่ยวข้องจะมีการกำหนดไว้แล้วสำหรับs (เช่น)valueUInt32RawOptionSetfunc |<T : RawOptionSet>(a: T, b: T) -> T
เดวิดลอว์สัน

ขอบคุณจุดที่ยอดเยี่ยมเกี่ยวกับฟังก์ชั่น - ฉันคิดว่าคอมไพเลอร์กำลังบ่นเกี่ยวกับสิ่งเหล่านั้นเมื่อฉันไม่ได้มีส่วนที่สอดคล้องของโปรโตคอล คุณเห็นปัญหาอะไรUIntบ้าง มันทำงานได้ดีสำหรับฉัน
Nate Cook

2
มีวิธีแก้ปัญหาที่ใช้ enum แทนที่จะเป็น struct หรือไม่? ฉันต้องเหมืองจะเข้ากันได้โดยมีวัตถุประสงค์-c ...
jowie

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

1
ในตัวอย่างนี้เอกสารของ Appleนั้นดีจริงๆ
Mr Rogers

12

Xcode 6.1 Beta 2 นำการเปลี่ยนแปลงบางอย่างไปใช้กับRawOptionSetTypeโปรโตคอล (ดูรายการบล็อก Airspeedvelocityนี้และบันทึกย่อประจำรุ่นของ Apple )

จากตัวอย่างของ Nate Cooks ที่นี่เป็นโซลูชันที่ได้รับการอัปเดต คุณสามารถกำหนดชุดตัวเลือกของคุณเองดังนี้:

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

จากนั้นจะสามารถใช้สิ่งนี้เพื่อกำหนดตัวแปร:

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

และเช่นนี้เพื่อทดสอบบิต:

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

ตัวอย่าง Swift 2.0 จากเอกสาร:

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

คุณสามารถค้นหาได้ที่นี่


6

ใน Swift 2 (ขณะนี้เบต้าเป็นส่วนหนึ่งของ Xcode 7 เบต้า) NS_OPTIONSประเภทของสไตล์จะถูกนำเข้าเป็นชนิดย่อยของOptionSetTypeประเภทใหม่ และด้วยคุณสมบัติส่วนขยายโปรโตคอลใหม่และวิธีOptionSetTypeการใช้งานในไลบรารีมาตรฐานคุณสามารถประกาศประเภทของคุณเองที่ขยายOptionsSetTypeและรับฟังก์ชั่นและวิธีการเดียวกันทั้งหมดที่นำเข้าNS_OPTIONSประเภทสไตล์ที่ได้รับ

แต่ฟังก์ชั่นเหล่านั้นไม่ได้ขึ้นอยู่กับตัวดำเนินการทางคณิตศาสตร์แบบบิตอีกต่อไป การทำงานกับชุดของตัวเลือกบูลีนที่ไม่ใช่แบบเอกสิทธิ์เฉพาะบุคคลใน C นั้นจำเป็นต้องมีการปิดบังและทวิบิตในฟิลด์นั้นเป็นรายละเอียดการนำไปปฏิบัติ จริงๆชุดของตัวเลือกคือชุด ... ชุดของรายการที่ไม่ซ้ำกัน ดังนั้นOptionsSetTypeได้รับวิธีการทั้งหมดจากSetAlgebraTypeโปรโตคอลเช่นการสร้างจาก array อักษรไวยากรณ์แบบสอบถามชอบcontainsกำบังกับintersectionฯลฯ (ไม่ต้องจำที่ตัวละครตลกที่จะใช้สำหรับการทดสอบซึ่งสมาชิก!)


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 และเพียงต้องการความหมายพื้นผิวของบิตมาสก์ใน Swift ฉันได้เขียน "ไลบรารี่" อย่างง่าย ๆ ที่เรียกว่า BitwiseOptions ที่สามารถทำได้ด้วยการแจกแจงปกติของ Swift เช่น:

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

และอื่น ๆ ไม่มีการพลิกบิตจริงที่นี่ เหล่านี้เป็นการตั้งค่าการดำเนินงานกับค่าทึบแสง คุณสามารถหาเค้าที่นี่


@ChrisPrince เป็นไปได้มากว่าเป็นเพราะสร้างขึ้นสำหรับ Swift 1.0 และยังไม่ได้รับการอัปเดตตั้งแต่นั้นมา
เกรกอรี่ Higley

ฉันกำลังใช้งาน Swift 2.0 เวอร์ชั่นนี้อยู่
เกรกอรี่ Higley

2

ตามที่ Rickster ได้กล่าวมาแล้วคุณสามารถใช้OptionSetTypeใน Swift 2.0 ประเภท NS_OPTIONS รับการนำเข้าเป็นไปตามOptionSetTypeโปรโตคอลซึ่งนำเสนออินเทอร์เฟซแบบ set-like สำหรับตัวเลือก:

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

มันให้วิธีการทำงานกับคุณ:

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

หากฟังก์ชันการทำงานเดียวที่เราต้องการคือวิธีรวมตัวเลือกด้วย|และตรวจสอบว่าตัวเลือกแบบรวมมีตัวเลือกเฉพาะพร้อม&คำตอบของ Nate Cook หรือไม่?

สร้างตัวเลือกprotocolและโอเวอร์โหลด|และ&:

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

ตอนนี้เราสามารถสร้างตัวเลือก structs ได้ง่ายขึ้นเช่น:

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

พวกเขาสามารถใช้ดังนี้

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

myMethod(options: .One | .Three) 

2

เพียงโพสต์ตัวอย่างเพิ่มเติมสำหรับคนอื่นที่สงสัยว่าคุณสามารถรวมตัวเลือกผสมได้หรือไม่ คุณสามารถและพวกเขารวมกันอย่างที่คุณคาดหวังถ้าคุณคุ้นเคยกับ bitfields เก่าที่ดี:

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

มันแผ่ชุด[.AB, .X]เป็น[.A, .B, .X](อย่างน้อยความหมาย):

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

1

ไม่มีใครพูดถึงมัน - และฉันก็รู้สึกผิดเพี้ยนไปจากมันหลังจากการซ่อมแซม - แต่ดูเหมือนว่า Swift Set จะทำงานได้ค่อนข้างดี

หากเราคิดว่า (อาจเป็นไดอะแกรม Venn?) เกี่ยวกับสิ่งที่บิตมาสก์กำลังแสดงอยู่มันเป็นเซตที่ว่างเปล่า

แน่นอนในการเข้าถึงปัญหาจากหลักการแรกเราสูญเสียความสะดวกสบายของผู้ประกอบการระดับบิต แต่ได้รับวิธีการตั้งค่าที่มีประสิทธิภาพซึ่งช่วยเพิ่มความสามารถในการอ่าน

นี่คือตัวอย่างการซ่อมของฉัน:

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

ฉันพบว่าสิ่งนี้ดีเพราะฉันรู้สึกว่ามันมาจากวิธีการแรกในการแก้ปัญหา - เช่นเดียวกับ Swift - แทนที่จะพยายามปรับแก้ปัญหาแบบ C

ยังต้องการที่จะได้ยินกรณีใช้ Obj-C บางอย่างที่จะท้าทายกระบวนทัศน์ที่แตกต่างกันนี้ซึ่งค่าดิบจำนวนเต็มยังคงแสดงให้เห็นถึงบุญ


1

เพื่อหลีกเลี่ยงยากที่เข้ารหัสตำแหน่งบิตซึ่งหลีกเลี่ยงไม่ได้เมื่อใช้(1 << 0), (1 << 1), (1 << 15)ฯลฯ หรือแม้แต่เลว1, 2, 16384ฯลฯ หรือบางรูปแบบเลขฐานสิบหกคนแรกที่สามารถกำหนดบิตในนั้นenumแล้วให้กล่าว enum คำนวณลำดับบิต:

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

เพิ่งเพิ่มตัวอย่างที่คุณไม่ต้องใช้รหัสยากอะไร
Peter Ahlberg

1

ฉันใช้สิ่งต่อไปนี้ฉันต้องการค่าทั้งสองที่ฉันจะได้รับ rawValue สำหรับการทำดัชนีอาร์เรย์และค่าสำหรับแฟล็ก

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

และหากต้องการมากกว่าเพียงแค่เพิ่มคุณสมบัติที่คำนวณได้

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: Sandbox และการสร้างคั่นหน้าโดยใช้ชุดตัวเลือกที่มีหลายตัวเลือก

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

โซลูชันที่ต้องรวมตัวเลือกสำหรับการสร้างสรรค์มีประโยชน์เมื่อตัวเลือกทั้งหมดไม่ได้มีความพิเศษร่วมกัน


0

คำตอบของ Nateนั้นดี แต่ฉันจะทำให้เป็น DIY เช่น:

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

ใช้ประเภทชุดตัวเลือกในการใช้งาน 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]
}

1
นี้จะมากหรือน้อยอยู่แล้วครอบคลุมอยู่ในคำตอบนี้
ปาง
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.