Как создать 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"
}
}
}