Déclarer et utiliser un champ de bits enum dans Swift

Comment les champs de bits doivent-ils être déclarés et utilisés dans Swift?

Déclarer une énumération comme celle-ci fonctionne, mais essayer de ou 2 valeurs ensemble échoue à compiler:

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

J'ai regardé comment Swift importe les types D'enum Foundation, et il le fait en définissant un struct conforme au Protocole RawOptionSet:

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

Et le protocole RawOptionSet est:

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

Cependant, il n'y a pas de documentation sur ce protocole et je ne peux pas comprendre comment l'implémenter moi-même. En outre, il n'est pas clair si c'est la manière rapide officielle d'implémenter des champs de bits ou si c'est seulement ainsi que le pont Objective-C Les représente.

32
demandé sur Pascal Bourque 2014-06-09 04:42:43

13 réponses

Vous pouvez construire un struct conforme au Protocole RawOptionSet, et vous pourrez l'utiliser comme le type enum intégré, mais avec la fonctionnalité bitmask. La réponse ici montre comment: énumérations Swift NS_OPTIONS-style bitmask .

24
répondu Nate Cook 2017-05-23 12:26:07

Ils ont montré comment faire dans l'une des vidéos de la WWDC.

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

Notez que {[1] } sera de type Int et obtiendra en fait une erreur de compilation si vous spécifiez let combined: MyEnum. C'est parce qu'il n'y a pas de valeur enum pour 0x05 qui est le résultat de l'expression.

13
répondu John Estropia 2014-06-09 01:09:17

Je pense que certaines des réponses ici sont obsolètes avec des solutions trop compliquées? Cela fonctionne très bien pour moi..

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
}
11
répondu Johannes 2014-12-23 15:22:52

Mise à jour pour Swift 2/3

Depuis swift 2, une nouvelle solution a été ajoutée en tant que "raw option set" (voir: Documentation), qui est essentiellement la même que ma réponse d'origine, mais en utilisant des structures qui autorisent des valeurs arbitraires.

C'est la question originale réécrite en tant que 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]

Combinant avec les nouvelles valeurs peuvent être faites exactement comme Set opérations (donc l'OptionSet partie), .union, de même:

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

Même chose que faire One | Four dans de son C l'équivalent. Comme pour One & Mask != 0, peut être spécifié comme une intersection non vide

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

Curieusement, la plupart des énumérations binaires de style C ont été converties en leur équivalent OptionSet sur Swift 3, mais Calendar.Compontents supprime un Set<Enum>:

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

Alors que l'original NSCalendarUnit était une énumération binaire. Donc, les deux approches sont utilisables (donc la réponse originale reste valide)

Réponse Originale

Je pense que la meilleure chose à faire est d'éviter simplement la syntaxe du masque de bits jusqu'à ce que les développeurs Swift trouvent un meilleur moyen.

La Plupart du temps, le problème peut être résolu à l'aide d'un enum et Set

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

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

An et check (options & .A) peuvent être définis comme:

options.contains(.A)

Ou pour plusieurs "drapeaux" pourrait être:

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

Ajout de nouveaux drapeaux (options |= .C):

options.insert(.C)

Cela permet également d'utiliser tous les nouveaux éléments avec des énumérations: types personnalisés,correspondance de motifs avec boîtier de commutation, etc.

Bien sûr, il n'a pas l'efficacité de bitwise opérations, ni il ne serait compatible avec des choses de bas niveau (comme l'envoi de commandes bluetooth), mais il est utile pour les éléments de L'interface utilisateur que la surcharge de l'interface utilisateur l'emporte sur le coût des opérations définies.

10
répondu Can 2016-12-21 19:54:31

Si vous n'avez pas besoin d'interopérer avec Objective-C et que vous voulez juste la syntaxe des masques de bits dans Swift, j'ai écrit une simple "bibliothèque" appelée BitwiseOptions qui peut le faire avec des énumérations Swift régulières, par exemple:

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

Et ainsi de suite. Pas de réelle bits sont retournées ici. Ce sont des opérations définies sur des valeurs opaques. Vous pouvez trouver l'essentiel ici.

7
répondu Gregory Higley 2014-11-24 17:09:01

Le très célèbre "NSHipster" de@Mattt a une description détaillée détaillée duRawOptionsSetType : http://nshipster.com/rawoptionsettype/

Il comprend un Xcode pratique 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) }
    // ...
}
3
répondu Klaas 2014-09-12 21:17:12

, Vous devez utiliser .toRaw() après chaque membre:

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

Fonctionnera. Parce que comme c'est le cas, vous essayez simplement d'attribuer "One" qui est un type MyEnum, Pas un entier. Comme la documentation d'Apple dit:

" contrairement à C et Objective-C, Les membres D'énumération Swift ne reçoivent pas de valeur entière par défaut lorsqu'ils sont créés. Dans L'exemple de CompassPoints, le Nord, le Sud, L'Est et L'Ouest ne sont pas implicitement égaux à 0, 1, 2 et 3. Au lieu de cela, les différents membres de l'énumération sont des valeurs à part entière à part entière, avec un type explicitement défini de CompassPoint."

Vous devez donc utiliser des valeurs brutes si vous voulez que les membres représentent un autre type, comme décrit ici:

Les membres D'énumération peuvent être pré-remplis avec des valeurs par défaut (appelées valeurs brutes), qui sont toutes du même type. La valeur brute pour un membre d'énumération particulier est toujours la même. Les valeurs brutes peuvent être des chaînes, des caractères ou l'un des entiers ou nombre à virgule flottante types. Chaque valeur brute doit être unique dans sa déclaration d'énumération. Lorsque des entiers sont utilisés pour les valeurs brutes, ils s'incrémentent automatiquement si aucune valeur n'est spécifiée pour certains membres de l'énumération. Accédez à la valeur brute d'un membre d'énumération avec sa méthode toRaw.

2
répondu SFX 2014-06-16 11:37:01

Je suppose que quelque chose comme ceci est la façon dont ils modélisent les options enum dans 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")
}

...où myOptions et {[2] } sont de type TestOptions-Je ne sais pas exactement comment fromMask() et getLogicValue() sont censés agir ici (j'ai juste pris quelques meilleures suppositions), peut-être que quelqu'un pourrait le ramasser et le résoudre?

1
répondu fqdn 2014-06-09 02:00:19

Si vous voulez bitfield dans Swift, alors enum est le mauvais chemin. Mieux vaut faire comme ça

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

Vous N'avez pas vraiment besoin du wrapper class / static, mais je l'inclus comme une sorte de pseudo-espace de noms.

1
répondu John Henckel 2015-08-09 16:43:50

Effectuez une opération au niveau du BIT en utilisant la valeur brute, puis créez un nouvel objet enum en utilisant le résultat.

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

0
répondu Vishun 2016-01-31 09:58:46

Voici quelque chose que j'ai mis en place pour essayer de faire une énumération Swift qui ressemble dans une certaine mesure à une énumération de style C# flags. Mais j'apprends juste Swift, donc cela ne devrait être considéré que comme un code" preuve de concept".

/// 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
   }
}

Cela montre comment il peut être utilisé:

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

Les Suggestions d'amélioration sont les bienvenues.

EDIT: j'ai moi-même abandonné cette technique, et donc évidemment je ne peux plus la recommander.

Le gros problème est que Swift exige que rawValue correspondre à l'une des valeurs enum. C'est OK s'il n'y a que 2 ou 3 ou peut - être même 4 bits de drapeau-définissez simplement toutes les valeurs de combinaison afin de rendre Swift heureux. Mais pour 5 bits de drapeau ou plus, il devient totalement fou.

Je vais laisser ceci posté au cas où quelqu'un le trouverait utile, ou peut-être comme un avertissement de la façon de ne pas le faire.

Ma solution actuelle à cette situation est basée sur l'utilisation d'une structure au lieu d'enum, avec un protocole et des méthodes d'extension. Cela fonctionne beaucoup mieux. Peut-être que je vais poster un jour quand je suis plus sûr que ce n'est pas aussi ne va pas se retourner contre moi.

0
répondu RenniePet 2017-01-28 04:31:55

J'utilise ce qui suit j'ai besoin des deux valeurs que je peux obtenir, rawValue pour l'indexation des tableaux et value pour les drapeaux.

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
0
répondu Dis3buted 2017-07-31 10:42:56

Cela a fonctionné pour moi.

  • 1
  • 1
  • 1
  • 1

      enum Collision: Int {
        case Enemy, Projectile, Debris, Ground
        func bitmask() -> UInt32 {
            return 1 << self.rawValue
          }
      }
    
0
répondu Evans Domina Attafuah 2018-09-15 06:16:38