Как создать NSmap в стиле битмакс в Swift?

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

Далее, он говорит об NS_OPTIONS -знаках C-style:

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

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

Ответ 1

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: MyOption = []

Другое использование:

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, которые были импортированы Swift (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.

Ответ 2

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

Ответ 3

Пример 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]
}

Здесь вы можете найти

Ответ 4

В Swift 2 (в настоящее время бета-версия в составе бета-версии Xcode 7) типы типов NS_OPTIONS импортируются как подтипы нового типа OptionSetType. И благодаря новой функции Расширения протокола и пути 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`
}

Ответ 6

Если вам не нужно взаимодействовать с 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!")
}

и т.д. Здесь нет реальных битов. Они задают операции с непрозрачными значениями. Вы можете найти gist здесь.

Ответ 7

Если единственная функциональность, которая нам нужна, - это способ объединить параметры с | и проверить, содержит ли объединенные опции конкретную опцию с &, альтернативой ответам 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) 

Ответ 8

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

Ответ 9

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

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

Ответ 10

Никто еще не упомянул об этом - и я как бы ошибался в этом после некоторого возиться - но Swift Set, похоже, работает достаточно хорошо.

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

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

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

enum Toppings : String {
    // Just strings 'cause there 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, которые будут оспаривать эту различную парадигму, где целые необработанные значения по-прежнему показывают достоинства.

Ответ 11

Чтобы избежать жесткого кодирования позиций битов, что неизбежно при использовании (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)
}

Ответ 12

re: песочница и создание заметок с помощью наборов опций с несколькими параметрами

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

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

Ответ 13

Ответ 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)
}

Ответ 14

Используйте опцию Set Type Type, в 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]
}

Ответ 15

Я использую следующее. Мне нужны оба значения, которые я могу получить, 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"
        }
    }
}