Як створити перерахунки бітових масок у стилі NS_OPTIONS у Swift?


137

У документації Apple про взаємодію з API-інтерфейсами C вони описують способи перерахування NS_ENUMпозначених у стилі C стилів як імпульсні перерахування. Це має сенс, і оскільки перерахування в Swift легко подаються як enumтип значення, легко зрозуміти, як створити нашу власну.

Далі внизу йдеться про параметри, що NS_OPTIONSпозначаються в стилі C:

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

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


3
В дуже відомому "NSHipster" Метта є великий опис RawOptionsSetType: nshipster.com/rawoptionsettype
Клаас,

Відповіді:


258

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

Швидкий 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")
}

Швидкий 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
David Lawson

Дякую, чудовий момент щодо функцій - я думаю, що компілятор скаржився на тих, коли у мене не було решти відповідності протоколу. З якими проблемами ви бачились UInt? Для мене це добре працює.
Нейт Кук

2
Чи є рішення, яке використовує enum замість структура? Мені потрібна моя, щоб бути сумісною з aim-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 ).

На основі прикладу 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типи -style імпортуються як підтипи нового OptionSetTypeтипу. І завдяки новій функції розширення протоколу та способу OptionSetTypeреалізації в стандартній бібліотеці, ви можете оголосити власні типи, що розширюються, OptionsSetTypeі отримувати всі ті ж функції та методи, які NS_OPTIONSотримують імпортовані типи стилів.

Але ці функції вже не базуються на побитових арифметичних операторах. Те, що робота з набором неексклюзивних булевих параметрів на 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 працює досить добре.

Якщо ми подумаємо (можливо, на діаграмі Венна?) Про те, що насправді представляє бітова маска, це, можливо, порожній набір.

Звичайно, підходячи до проблеми з перших принципів, ми втрачаємо зручність операцій з побітними операціями, але отримуємо потужні методи на основі набору, що покращує читабельність.

Ось, наприклад, моє роздуття:

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, а не до спроб адаптувати рішення в стилі С.

Також хотілося б почути деякі випадки використання 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
Це більш-менш вже висвітлено у цій відповіді .
Панг
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.