Làm cách nào để tạo bảng liệt kê bitmask kiểu NS_OPTIONS trong Swift?


137

Trong tài liệu của Apple về việc tương tác với API C, họ mô tả NS_ENUMcách liệt kê kiểu C được đánh dấu được nhập dưới dạng liệt kê Swift. Điều này có ý nghĩa và vì các liệt kê trong Swift dễ dàng được cung cấp dưới enumdạng loại giá trị, thật dễ dàng để xem cách tạo riêng của chúng tôi.

Xa hơn nữa, nó nói điều này về NS_OPTIONScác tùy chọn kiểu chữ C được đánh dấu:

Swift cũng nhập các tùy chọn được đánh dấu bằng NS_OPTIONSmacro. Trong khi lựa chọn ứng xử tương tự như enumerations nhập khẩu, tùy chọn này cũng có thể hỗ trợ một số hoạt động Bitwise, chẳng hạn như &, |, và ~. Trong Objective-C, bạn biểu thị một tùy chọn trống được đặt với hằng số zero ( 0). Trong Swift, sử dụng nilđể thể hiện sự vắng mặt của bất kỳ tùy chọn nào.

Vì không có optionsloại giá trị trong Swift, làm thế nào chúng ta có thể tạo biến tùy chọn Kiểu C để làm việc?


3
"NSHipster" rất nổi tiếng của Mattt có một mô tả mở rộng về RawOptionsSetType: nshipster.com/rawoptionsettype
Klaas

Câu trả lời:


258

Swift 3.0

Gần giống với Swift 2.0. OptionSetType đã được đổi tên thành Tùy chọn và enums được viết bằng chữ thường theo quy ước.

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

Thay vì cung cấp một nonetùy chọn, khuyến nghị của Swift 3 chỉ đơn giản là sử dụng một mảng trống theo nghĩa đen:

let noOptions: MyOptions = []

Cách sử dụng khác:

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

Trong Swift 2.0, các phần mở rộng giao thức đảm nhiệm hầu hết các mẫu soạn sẵn cho các phần mở rộng này, hiện được nhập dưới dạng cấu trúc phù hợp với OptionSetType. ( RawOptionSetTypeđã biến mất kể từ Swift 2 beta 2.) Việc khai báo đơn giản hơn nhiều:

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

Bây giờ chúng ta có thể sử dụng ngữ nghĩa dựa trên tập hợp với 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

Nhìn vào các tùy chọn Objective-C được nhập khẩu bởi Swift ( UIViewAutoresizingchẳng hạn), chúng ta có thể thấy rằng tùy chọn được khai báo là một structmà phù hợp với giao thức RawOptionSetType, mà trong chiếu theo lần lượt _RawOptionSetType, Equatable, RawRepresentable, BitwiseOperationsType, và NilLiteralConvertible. Chúng ta có thể tạo riêng của chúng tôi như thế này:

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

Bây giờ chúng tôi có thể xử lý bộ tùy chọn mới này MyOptions, giống như được mô tả trong tài liệu của Apple: bạn có thể sử dụng enumcú pháp giống như:

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

Và nó cũng hoạt động như chúng ta mong đợi các tùy chọn để hành xử:

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ôi đã xây dựng một trình tạo để tạo một tùy chọn Swift mà không cần tìm / thay thế.

Mới nhất: Sửa đổi cho Swift 1.1 beta 3.


1
Nó không làm việc cho tôi, trừ khi tôi làm valuemột UInt32. Bạn cũng không cần xác định bất kỳ chức năng nào, các chức năng có liên quan đã được xác định cho RawOptionSets (ví dụ func |<T : RawOptionSet>(a: T, b: T) -> T)
David Lawson

Cảm ơn, điểm tuyệt vời về các chức năng - Tôi nghĩ rằng trình biên dịch đã phàn nàn về những chức năng đó khi tôi không có phần còn lại của sự phù hợp giao thức. Những vấn đề bạn đã thấy với UInt? Nó làm việc tốt với tôi.
Nate Cook

2
Có một giải pháp sử dụng enum thay vì struct? Tôi cần của tôi để tương thích với mục tiêu-c ...
jowie 4/12/2015

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

1
Trong trường hợp này, tài liệu của Apple thực sự tốt.
Ông Rogers

12

Xcode 6.1 Beta 2 mang lại một số thay đổi cho RawOptionSetTypegiao thức (xem mục blog Airspeedvelocity này và ghi chú phát hành của Apple ).

Dựa trên ví dụ của Nate Cooks ở đây là một giải pháp cập nhật. Bạn có thể xác định tùy chọn của riêng mình được đặt như thế này:

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

Sau đó, nó có thể được sử dụng như thế này để xác định các biến:

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

Và như thế này để kiểm tra bit:

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

Ví dụ Swift 2.0 từ tài liệu:

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

Bạn có thể tìm thấy nó ở đây


6

Trong Swift 2 (hiện đang là phiên bản beta của Xcode 7 beta), các NS_OPTIONSkiểu kiểu được nhập dưới dạng các OptionSetTypekiểu con của loại mới . Và nhờ tính năng Tiện ích mở rộng giao thức mới và cách OptionSetTypeđược triển khai trong thư viện chuẩn, bạn có thể khai báo các loại mở rộng của riêng mình OptionsSetTypevà nhận tất cả các chức năng và phương thức tương tự đã nhậpNS_OPTIONS kiểu kiểu có được.

Nhưng các hàm đó không dựa trên các toán tử số học bitwise nữa. Việc làm việc với một tập hợp các tùy chọn Boolean không độc quyền trong C yêu cầu che dấu và vặn các bit trong một trường là một chi tiết triển khai. Thực sự, một tập hợp các tùy chọn là một bộ ... một tập hợp các vật phẩm độc đáo. Vì vậy, OptionsSetTypecó tất cả các phương thức từ SetAlgebraTypegiao thức, như tạo từ cú pháp mảng, truy vấn như contains, che dấu intersection, v.v. (Không cần phải nhớ ký tự vui nào để sử dụng cho bài kiểm tra thành viên nào!)


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

Nếu bạn không cần phải tương tác với Objective-C và chỉ muốn ngữ nghĩa bề mặt của mặt nạ bit trong Swift, tôi đã viết một "thư viện" đơn giản có tên BitwiseOptions có thể thực hiện điều này với bảng liệt kê Swift thông thường, ví dụ:

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

và như thế. Không có bit thực tế đang được lật ở đây. Đây là các hoạt động thiết lập trên các giá trị mờ. Bạn có thể tìm thấy ý chính ở đây .


@ChrisPrince Rất có thể đó là vì nó được tạo cho Swift 1.0 và chưa được cập nhật kể từ đó.
Gregory Higley

Tôi thực sự đang làm việc trên một phiên bản Swift 2.0 này.
Gregory Higley

2

Như Rickster đã đề cập, bạn có thể sử dụng OptionSetType trong Swift 2.0. Các loại NS_OPTIONS được nhập dưới dạng tuân thủ OptionSetTypegiao thức, trình bày giao diện giống như tập hợp cho các tùy chọn:

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

Nó cung cấp cho bạn cách làm việc này:

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

Nếu chức năng duy nhất chúng ta cần là một cách để kết hợp các tùy chọn |và kiểm tra xem các tùy chọn kết hợp có chứa một tùy chọn cụ thể với& một lựa chọn thay thế cho câu trả lời của Nate Cook có thể là:

Tạo một tùy chọn protocolvà quá tả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
    }
}

Bây giờ chúng ta có thể tạo các tùy chọn cấu trúc đơn giản hơn như vậy:

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

Chúng có thể được sử dụng như sau:

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

myMethod(options: .One | .Three) 

2

Chỉ cần đăng một ví dụ bổ sung cho bất cứ ai khác đang tự hỏi nếu bạn có thể kết hợp các tùy chọn ghép. Bạn có thể và chúng kết hợp như bạn mong đợi nếu bạn đã quen với các bitfield cũ tốt:

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

Nó làm phẳng tập hợp [.AB, .X]thành [.A, .B, .X](ít nhất là về mặt ngữ nghĩa):

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

1

Không ai khác đã đề cập đến nó - và tôi hơi ngớ ngẩn với nó sau khi mày mò - nhưng Swift Set dường như hoạt động khá tốt.

Nếu chúng ta nghĩ (có thể với sơ đồ Venn?) Về những gì một mặt nạ bit thực sự đại diện, thì đó là một tập hợp có thể trống.

Tất nhiên, khi tiếp cận vấn đề từ các nguyên tắc đầu tiên, chúng ta mất đi sự tiện lợi của các toán tử bitwise, nhưng có được các phương thức dựa trên tập hợp mạnh mẽ giúp cải thiện khả năng đọc.

Đây là sự mày mò của tôi chẳng hạn:

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

Tôi thấy điều này tốt bởi vì tôi cảm thấy nó xuất phát từ cách tiếp cận nguyên tắc đầu tiên cho vấn đề - giống như Swift - thay vì cố gắng điều chỉnh các giải pháp theo kiểu C.

Cũng muốn nghe một số trường hợp sử dụng Obj-C sẽ thách thức mô hình khác nhau này, trong đó các giá trị thô nguyên vẫn hiển thị công đức.


1

Để tránh cứng mã hóa các vị trí bit, đó là không thể tránh khỏi khi sử dụng (1 << 0), (1 << 1), (1 << 15)vv hoặc thậm chí tệ hơn 1, 2, 16384vv hoặc một số biến thể hệ thập lục phân, lần đầu tiên người ta có thể định nghĩa các bit trong một enum, sau đó hãy để cho biết enum làm việc tính toán thứ tự bit:

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

Chỉ cần thêm ví dụ khi bạn không phải mã cứng bất cứ thứ gì.
Peter Ahlberg

1

Tôi sử dụng sau đây tôi cần cả hai giá trị tôi có thể nhận được, rawValue để lập chỉ mục mảng và giá trị cho các cờ.

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

Và nếu một người cần nhiều hơn chỉ cần thêm một tài sản tính toán.

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 và đánh dấu sáng tạo bằng cách sử dụng bộ tùy chọn với một số tùy chọn

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

giải pháp cần kết hợp các tùy chọn cho sáng tạo, hữu ích khi không phải tất cả các tùy chọn đều loại trừ lẫn nhau.


0

Câu trả lời của Nate là tốt nhưng tôi sẽ làm nó DIY, như vậy:

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

Sử dụng Loại tùy chọn, trong 3 lần sử dụng 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
Điều này ít nhiều đã được đề cập trong câu trả lời này .
Pang
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.