Как создать перечисления битовых масок в стиле NS_OPTIONS в Swift?


137

В документации Apple о взаимодействии с C API описывается, как NS_ENUMотмеченные обозначения перечисления в стиле C импортируются как перечисления Swift. В этом есть смысл, и поскольку перечисления в Swift легко предоставляются как enumтип значения, легко увидеть, как создать свои собственные.

NS_OPTIONSНиже говорится об опциях C-стиля с пометкой:

Swift также импортирует параметры, отмеченные NS_OPTIONSмакросом. В то время как варианты ведут себя так же , как импортируемые перечисления, варианты могут также поддерживать некоторые битовые операции, такие как &, |, и ~. В Objective-C вы представляете пустой набор параметров с константой ноль ( 0). В Swift используйте nilдля обозначения отсутствия каких-либо параметров.

Учитывая, что optionsв Swift нет типа значения, как мы можем создать переменную параметров C-Style для работы?


3
В очень известном "NSHipster" @ Мэтта есть подробное описание RawOptionsSetType: nshipster.com/rawoptionsettype
Клаас

Ответы:


261

Swift 3.0

Практически идентичен Swift 2.0. OptionSetType был переименован в OptionSet, и перечисления по соглашению записываются в нижнем регистре.

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

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

Теперь мы можем использовать семантику на основе наборов с 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

Глядя на варианты 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синтаксис -like:

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
Есть ли решение, использующее перечисление вместо структуры? Мне нужна моя, чтобы она была совместима с objective-c ...
jowie

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

1
В этом случае документы Apple действительно хороши.
Мистер Роджерс

12

Xcode 6.1 Beta 2 внес некоторые изменения в RawOptionSetTypeпротокол (см. Эту запись в блоге Airspeedvelocity и примечания к выпуску Apple ).

Вот обновленное решение, основанное на примере Нейта Кука. Вы можете определить свой собственный набор опций следующим образом:

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типы -style импортируются как подтипы нового OptionSetTypeтипа. А благодаря новой функции Protocol Extensions и способу OptionSetType, реализованному в стандартной библиотеке, вы можете объявлять свои собственные типы, которые расширяются OptionsSetTypeи получают все те же функции и методы, что и импортированные NS_OPTIONSтипы -style.

Но эти функции больше не основаны на побитовых арифметических операторах. То, что для работы с набором неисключительных логических опций в C требуется маскирование и подстановка битов в поле, является деталью реализации. На самом деле набор опций - это набор ... набор уникальных предметов. Таким образом, OptionsSetTypeполучает все методы из SetAlgebraTypeпротокола, такие как создание из синтаксиса литерала массива, такие запросы, как 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 и с тех пор не обновлялся.
Грегори Хигли

На самом деле я работаю над версией этого Swift 2.0.
Грегори Хигли

2

Как уже упоминал Рикстер, вы можете использовать OptionSetType в Swift 2.0. Типы NS_OPTIONS импортируются как соответствующие OptionSetTypeпротоколу, который представляет собой наборный интерфейс для параметров:

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

Если единственная функция, которая нам нужна, - это способ комбинировать параметры |и проверять, содержат ли комбинированные параметры конкретную опцию, с &альтернативой Нейту Куку, ответ может быть следующим:

Создайте параметры 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
    }
}

Теперь мы можем создавать структуры параметров более просто:

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

Просто разместите дополнительный пример для всех, кто задавался вопросом, можно ли комбинировать составные варианты. Вы можете, и они сочетаются, как и следовало ожидать, если вы привыкли к старым добрым битовым полям:

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, похоже, работает довольно хорошо.

Если мы подумаем (может быть, на диаграмме Венна?) О том, что на самом деле представляет битовая маска, это, возможно, пустой набор.

Конечно, подходя к проблеме из первых принципов, мы теряем удобство поразрядных операторов, но получаем мощные методы, основанные на наборах, которые улучшают читаемость.

Вот, например, моя работа:

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, то пусть говорит , перечисление сделать битовый порядковый расчет:

// 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: Создание песочницы и закладок с использованием наборов опций с несколькими опциями

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

решение необходимости комбинировать варианты создания, полезно, когда не все варианты являются взаимоисключающими.


0

Ответ Нейта хорош, но я бы сделал его своими руками, вот так:

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

Используйте тип набора параметров, в быстром 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
Это более или менее уже описано в этом ответе .
Pang
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.