Объявление и использование перечисления битового поля в Swift

Как должны быть объявлены и использованы битовые поля в Swift?

Объявление такого перечисления, как это, работает, но попытка слияния с OR 2 не может скомпилироваться:

enum MyEnum: Int
{
    case One =      0x01
    case Two =      0x02
    case Four =     0x04
    case Eight =    0x08
}

// This works as expected
let m1: MyEnum = .One

// Compiler error: "Could not find an overload for '|' that accepts the supplied arguments"
let combined: MyEnum = MyEnum.One | MyEnum.Four

Я посмотрел, как Swift импортирует типы перечисления Foundation, и он делает это, определяя struct, который соответствует протоколу RawOptionSet:

struct NSCalendarUnit : RawOptionSet {
    init(_ value: UInt)
    var value: UInt
    static var CalendarUnitEra: NSCalendarUnit { get }
    static var CalendarUnitYear: NSCalendarUnit { get }
    // ...
}

И протокол RawOptionSet:

protocol RawOptionSet : LogicValue, Equatable {
    class func fromMask(raw: Self.RawType) -> Self
}

Однако в этом протоколе нет документации, и я не могу понять, как ее реализовать. Более того, неясно, является ли это официальным способом Swift для реализации битовых полей или если это только то, что представляет собой мост Objective-C.

Ответ 1

Вы можете построить struct, который соответствует протоколу RawOptionSet, и вы сможете использовать его, как встроенный тип enum, но также и с помощью функции битовой маски. Ответ здесь показывает, как: Переменные перечисления в стиле Swift NS_OPTIONS.

Ответ 2

Они показали, как это сделать в одном из видео WWDC.

let combined = MyEnum.One.toRaw() | MyEnum.Four.toRaw()

Обратите внимание, что combined будет Int и будет получать ошибку компилятора, если вы укажете let combined: MyEnum. Это связано с тем, что для 0x05 нет значения enum, которое является результатом выражения.

Ответ 3

Я думаю, что, может быть, некоторые из ответов здесь устарели от чрезмерно сложных решений? Это отлично работает для меня.

enum MyEnum: Int  {

    case One = 0
    case Two = 1
    case Three = 2
    case Four = 4
    case Five = 8
    case Six = 16

}

let enumCombined = MyEnum.Five.rawValue | MyEnum.Six.rawValue

if enumCombined & MyEnum.Six.rawValue != 0 {
    println("yay") // prints
}

if enumCombined & MyEnum.Five.rawValue != 0 {
    println("yay again") // prints
}

if enumCombined & MyEnum.Two.rawValue != 0 {
    println("shouldn't print") // doesn't print
}

Ответ 4

Обновлено для Swift 2/3

Так как swift 2, новое решение было добавлено как "набор необработанных опций" (см. документацию), что по сути то же самое, что и мой первоначальный ответ, но используя структуры, которые позволяют произвольные значения.

Это оригинальный вопрос, переписанный как OptionSet:

struct MyOptions: OptionSet
{
    let rawValue: UInt8

    static let One = MyOptions(rawValue: 0x01)
    static let Two = MyOptions(rawValue: 0x02)
    static let Four = MyOptions(rawValue: 0x04)
    static let Eight = MyOptions(rawValue: 0x08)
}

let m1 : MyOptions = .One

let combined : MyOptions = [MyOptions.One, MyOptions.Four]

Совмещение с новыми значениями можно выполнить точно как операции Set (таким образом, параметр Установить), .union, равно как:

m1.union(.Four).rawValue // Produces 5

То же, что и One | Four в его C-эквиваленте. Что касается One & Mask != 0, можно указать как непустое пересечение

// Equivalent of A & B != 0
if !m1.intersection(combined).isEmpty
{
    // m1 belongs is in combined
}

Как ни странно, большинство побитовых перечислений C-стиля были преобразованы в их эквивалент OptionSet в Swift 3, но Calendar.Compontents устраняет Set<Enum>:

let compontentKeys : Set<Calendar.Component> = [.day, .month, .year]

В то время как исходный NSCalendarUnit был поразным перечислением. Таким образом, оба подхода применимы (таким образом, исходный ответ остается в силе)

Оригинальный ответ

Я думаю, что самое лучшее, что нужно сделать, - просто избегать синтаксиса битмаски, пока разработчики Swift не поймут лучший способ.

В большинстве случаев проблему можно решить с помощью enum и и Set

enum Options
{
    case A, B, C, D
}

var options = Set<Options>(arrayLiteral: .A, .D)

An и check (options & .A) можно определить как:

options.contains(.A)

Или для нескольких "флагов" может быть:

options.isSupersetOf(Set<Options>(arrayLiteral: .A, .D))

Добавление новых флагов (options |= .C):

options.insert(.C)

Это также позволяет использовать все новые вещи с перечислениями: настраиваемые типы, сопоставление шаблонов с корпусом коммутатора и т.д.

Конечно, он не имеет эффективности побитовых операций и не совместим с вещами низкого уровня (например, с отправкой команд bluetooth), но полезно для элементов пользовательского интерфейса, что накладные расходы пользовательского интерфейса перевешивают стоимость Задайте операции.

Ответ 5

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

Ответ 6

@Mattt очень известный "NSHipster" имеет подробное описание RawOptionsSetType: http://nshipster.com/rawoptionsettype/

Он включает в себя удобный Xcode snipped:

struct <# Options #> : RawOptionSetType, BooleanType {
    private var value: UInt = 0
    init(_ value: UInt) { self.value = value }
    var boolValue: Bool { return value != 0 }
    static func fromMask(raw: UInt) -> <# Options #> { return self(raw) }
    static func fromRaw(raw: UInt) -> <# Options #>? { return self(raw) }
    func toRaw() -> UInt { return value }
    static var allZeros: <# Options #> { return self(0) }
    static func convertFromNilLiteral() -> <# Options #> { return self(0) }

    static var None: <# Options #>          { return self(0b0000) }
    static var <# Option #>: <# Options #>  { return self(0b0001) }
    // ...
}

Ответ 7

Вы должны использовать .toRaw() после каждого члена:

let combined: Int = MyEnum.One.toRaw() | MyEnum.Four.toRaw()

будет работать. Поскольку вы просто пытаетесь назначить "Один", который является типом MyEnum, не целое число. Как Документация Apple говорит:

"В отличие от C и Objective-C членам Swift перечисления не присваивается целочисленное значение по умолчанию при их создании. В примере CompassPoints Север, Юг, Восток и Запад неявно не равны 0, 1, 2 и 3. Вместо этого различные члены перечисления являются полностью самостоятельными значениями с явно определенным типом CompassPoint.

поэтому вам нужно использовать необработанные значения, если вы хотите, чтобы члены представляли какой-то другой тип, как описано здесь:

Элементы перечисления могут быть предварительно заполнены значениями по умолчанию (называемыми необработанными значениями), которые являются одинаковыми. Необработанное значение для конкретного элемента перечисления всегда одно и то же. Необработанные значения могут быть строками, символами или любыми типами чисел с целыми числами или с плавающей запятой. Каждое необработанное значение должно быть уникальным в своей декларации перечисления. Когда для необработанных значений используются целые числа, они автоматически увеличиваются, если для некоторых элементов перечисления не задано значение. Получите доступ к исходному значению элемента перечисления с помощью метода toRaw.

Ответ 8

Я предполагаю, что это похоже на то, как они моделируют параметры перечисления в Foundation:

struct TestOptions: RawOptionSet {

    // conform to RawOptionSet
    static func fromMask(raw: UInt) -> TestOptions {
        return TestOptions(raw)
    }

    // conform to LogicValue
    func getLogicValue() -> Bool {
        if contains([1, 2, 4], value) {
            return true
        }
        return false
    }

    // conform to RawRepresentable
    static func fromRaw(raw: UInt) -> TestOptions? {
        if contains([1, 2, 4], raw) {
            return TestOptions(raw)
        }
        return nil
    }
    func toRaw() -> UInt {
        return value
    }

    // options and value
    var value: UInt
    init(_ value: UInt) {
        self.value = value
    }

    static var OptionOne: TestOptions {
        return TestOptions(1)
    }
    static var OptionTwo: TestOptions {
        return TestOptions(2)
    }
    static var OptionThree: TestOptions {
        return TestOptions(4)
    }
}

let myOptions = TestOptions.OptionOne | TestOptions.OptionThree
println("myOptions: \(myOptions.toRaw())")
if (myOptions & TestOptions.OptionOne) {
    println("OPTION ONE is in there")
} else {
    println("nope, no ONE")
}
if (myOptions & TestOptions.OptionTwo) {
    println("OPTION TWO is in there")
} else {
    println("nope, no TWO")
}
if (myOptions & TestOptions.OptionThree) {
    println("OPTION THREE is in there")
} else {
    println("nope, no THREE")
}

let nextOptions = myOptions | TestOptions.OptionTwo
println("options: \(nextOptions.toRaw())")
if (nextOptions & TestOptions.OptionOne) {
    println("OPTION ONE is in there")
} else {
    println("nope, no ONE")
}
if (nextOptions & TestOptions.OptionTwo) {
    println("OPTION TWO is in there")
} else {
    println("nope, no TWO")
}
if (nextOptions & TestOptions.OptionThree) {
    println("OPTION THREE is in there")
} else {
    println("nope, no THREE")
}

... где myOptions и nextOptions относятся к типу TestOptions - я не совсем уверен, как fromMask() и getLogicValue() должны действовать здесь (я просто принял некоторые предположения), возможно, кто-то мог забрать это и проработать?

Ответ 9

Если вы хотите бит-бит в Swift, то перечисление будет неправильным. Лучше просто сделайте это

class MyBits {
    static let One =      0x01
    static let Two =      0x02
    static let Four =     0x04
    static let Eight =    0x08
}
let m1 = MyBits.One
let combined = MyBits.One | MyBits.Four

Вам действительно не нужна оболочка class/static, но я включаю его как своего рода псевдопространство.

Ответ 10

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

let mask = UIViewAutoresizing(rawValue: UIViewAutoresizing.FlexibleWidth.rawValue|UIViewAutoresizing.FlexibleHeight.rawValue) self.view.autoresizingMask = mask

Ответ 11

Здесь кое-что, что я собрал, чтобы попытаться сделать переименование Swift, которое в какой-то мере напоминает переименование флагов С#. Но я просто изучаю Swift, поэтому это следует рассматривать только как код "доказательства концепции".

/// This EnumBitFlags protocol can be applied to a Swift enum definition, providing a small amount
/// of compatibility with the flags-style enums available in C#.
///
/// The enum should be defined as based on UInt, and enum values should be defined that are powers
/// of two (1, 2, 4, 8, ...). The value zero, if defined, should only be used to indicate a lack of
/// data or an error situation.
///
/// Note that with C# the enum may contain a value that does not correspond to the defined enum
/// constants. This is not possible with Swift, it enforces that only valid values can be set.
public protocol EnumBitFlags : RawRepresentable, BitwiseOperations {

   var rawValue : UInt { get }  // This provided automatically by enum

   static func createNew(_ rawValue : UInt) -> Self  // Must be defined as some boiler-plate code
}

/// Extension methods for enums that implement the EnumBitFlags protocol.
public extension EnumBitFlags {

   // Implement protocol BitwiseOperations. But note that some of these operators, especially ~, 
   // will almost certainly result in an invalid (nil) enum object, resulting in a crash.

   public static func & (leftSide: Self, rightSide: Self) -> Self {
      return self.createNew(leftSide.rawValue & rightSide.rawValue)
   }

   public static func | (leftSide: Self, rightSide: Self) -> Self {
      return self.createNew(leftSide.rawValue | rightSide.rawValue)
   }

   public static func ^ (leftSide: Self, rightSide: Self) -> Self {
      return self.createNew(leftSide.rawValue ^ rightSide.rawValue)
   }

   public static prefix func ~ (x: Self) -> Self {
      return self.createNew(~x.rawValue)
   }

   public static var allZeros: Self {
      get {
         return self.createNew(0)
      }
   }

   // Method hasFlag() for compatibility with C#
   func hasFlag<T : EnumBitFlags>(_ flagToTest : T) -> Bool {
      return (self.rawValue & flagToTest.rawValue) != 0
   }
}

Это показывает, как его можно использовать:

class TestEnumBitFlags {

   // Flags-style enum specifying where to write the log messages
   public enum LogDestination : UInt, EnumBitFlags {
      case none = 0             // Error condition
      case systemOutput = 0b01  // Logging messages written to system output file
      case sdCard       = 0b10  // Logging messages written to SD card (or similar storage)
      case both         = 0b11  // Both of the above options

      // Implement EnumBitFlags protocol
      public static func createNew(_ rawValue : UInt) -> LogDestination {
         return LogDestination(rawValue: rawValue)!
      }
   }

   private var _logDestination : LogDestination = .none
   private var _anotherEnum : LogDestination = .none

   func doTest() {

      _logDestination = .systemOutput
      assert(_logDestination.hasFlag(LogDestination.systemOutput))
      assert(!_logDestination.hasFlag(LogDestination.sdCard))

      _anotherEnum = _logDestination
      assert(_logDestination == _anotherEnum)

      _logDestination = .systemOutput | .sdCard
      assert(_logDestination.hasFlag(LogDestination.systemOutput) &&
             _logDestination.hasFlag(LogDestination.sdCard))

      /* don't do this, it results in a crash
      _logDestination = _logDestination & ~.systemOutput
      assert(_logDestination == .sdCard)
      */

      _logDestination = .sdCard
      _logDestination |= .systemOutput
      assert(_logDestination == .both)
   }
}

Предложения по улучшению приветствуются.

EDIT: Я сам отказался от этой техники и поэтому, очевидно, больше не могу ее рекомендовать.

Большая проблема в том, что Swift требует, чтобы rawValue соответствовал одному из определенных значений перечисления. Это нормально, если есть только 2 или 3 или, возможно, даже 4 бита флага - просто определите все значения комбинации, чтобы сделать Swift счастливым. Но для 5 или более бит бит он становится совершенно сумасшедшим.

Я оставлю это опубликовано, если кто-то найдет его полезным или может быть предупреждением о том, как НЕ делать это.

Мое текущее решение этой ситуации основано на использовании структуры вместо enum вместе с протоколом и некоторыми методами расширения. Это работает намного лучше. Может быть, я отправлю его когда-нибудь, когда я уверен, что это тоже не будет иметь неприятных последствий для меня.

Ответ 12

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