it-swarm.com.de

In Swift eine Bitfeld-Enumeration deklarieren und verwenden

Wie sollen Bitfelder in Swift deklariert und verwendet werden?

Das Deklarieren eines solchen Enums funktioniert zwar, aber der Versuch, 2 Werte zusammen zu OR zu setzen, kann nicht kompiliert werden:

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

Ich habe mir angesehen, wie Swift Foundation-Enummentypen importiert, und dies durch Definition einer struct, die dem RawOptionSet-Protokoll entspricht:

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

Das RawOptionSet-Protokoll lautet:

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

Es gibt jedoch keine Dokumentation zu diesem Protokoll und ich kann nicht herausfinden, wie ich es selbst implementieren kann. Darüber hinaus ist es nicht klar, ob dies die offizielle Methode zur schnellen Implementierung von Bitfeldern ist oder ob die Objective-C-Brücke nur so dargestellt wird.

35
Pascal Bourque

Sie können eine struct erstellen, die dem RawOptionSet-Protokoll entspricht, und Sie können es wie den integrierten enum-Typ verwenden, jedoch auch mit Bitmasken-Funktionalität. Die Antwort hier zeigt, wie: Swift-Bitmasken-Enumerationen im NS_OPTIONS-Stil .

25
Nate Cook

In einem der WWDC-Videos wurde gezeigt, wie das geht.

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

Beachten Sie, dass combined vom Typ Int ist und einen Compiler-Fehler erhält, wenn Sie let combined: MyEnum angeben. Das liegt daran, dass es keinen Aufzählungswert für 0x05 gibt, der das Ergebnis des Ausdrucks ist.

13
John Estropia

Aktualisiert für Swift 2/3

Seit Swift 2 wurde eine neue Lösung als "Rohoptionsgruppe" ( siehe: Dokumentation ) hinzugefügt. Diese ist im Wesentlichen die gleiche wie meine ursprüngliche Antwort, verwendet jedoch Strukturen, die beliebige Werte zulassen.

Dies ist die ursprüngliche Frage, die als OptionSet umgeschrieben wurde:

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]

Das Kombinieren mit neuen Werten kann genau wie Set-Operationen (also der OptionSet - Teil), .union erfolgen, ebenfalls:

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

Gleich wie One | Four in seinem C-Äquivalent. Wie für One & Mask != 0 kann eine nicht leere Kreuzung angegeben werden

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

Seltsamerweise wurden die meisten bitweisen Enumerationen im C-Stil unter Swift 3 in ihr OptionSet-Äquivalent konvertiert, aber Calendar.Compontents verzichtet auf einen Set<Enum>:

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

Während das Original NSCalendarUnit eine bitweise Enumeration war. Somit sind beide Ansätze verwendbar (somit bleibt die ursprüngliche Antwort gültig)

Ursprüngliche Antwort

Ich denke, das Beste ist, einfach die Bitmasken-Syntax zu umgehen, bis die Swift-Entwickler einen besseren Weg finden.

In den meisten Fällen kann das Problem mit einer enum und einer Set gelöst werden.

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

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

Ein und check (options & .A) könnte folgendermaßen definiert werden:

options.contains(.A)

Oder für mehrere "Flaggen" könnte es sein:

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

Hinzufügen neuer Flags (options |= .C):

options.insert(.C)

Dies ermöglicht auch die Verwendung aller neuen Elemente mit Aufzählungen: benutzerdefinierte Typen, Musterabgleich mit Switch-Case usw.

Natürlich hat es nicht die Effizienz bitweiser Operationen und wäre auch mit Low-Level-Dingen (wie dem Senden von Bluetooth-Befehlen) kompatibel, aber es ist nützlich für Elemente der Benutzeroberfläche, dass der Aufwand der Benutzeroberfläche die Kosten der Set-Operationen aufwiegt.

12
Can

Ich denke, dass einige der Antworten hier mit übermäßigen Lösungen veraltet sind? Das funktioniert gut für mich ..

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
Johannes

Wenn Sie nicht mit Objective-C zusammenarbeiten müssen und nur die syntax von Bitmasken in Swift benötigen, habe ich eine einfache "Bibliothek" namens BitwiseOptions geschrieben, die dies mit regulären Swift-Aufzählungen tun kann, z.

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

und so weiter. Hier werden keine echten Bits umgedreht. Dies sind Operationen, die auf undurchsichtige Werte eingestellt sind. Sie finden den Gist hier .

7
Gregory Higley

@ Mattts sehr berühmtes "NSHipster" hat eine ausführliche Beschreibung der RawOptionsSetType: http://nshipster.com/rawoptionsettype/

Es enthält einen praktischen Xcode-Ausschnitt:

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
Klaas

Sie müssen .toRaw () nach jedem Mitglied verwenden:

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

wird funktionieren. Da Sie gerade versuchen, "One", einem MyEnum-Typ, zuzuweisen, ist not eine Ganzzahl. Wie Apples Dokumentation sagt:

„Im Gegensatz zu C und Objective-C erhalten Swift-Enumerationsmitglieder beim Erstellen keinen Standardwert für die Ganzzahl. Im CompassPoints-Beispiel sind Nord, Süd, Ost und West nicht implizit gleich 0, 1, 2 und 3. Stattdessen sind die verschiedenen Enumerationsmitglieder vollwertige Werte mit einem explizit definierten Typ von CompassPoint. "

sie müssen also Rohwerte verwenden, wenn Sie möchten, dass die Elemente einen anderen Typ darstellen, wie beschrieben hier :

Aufzählungsmitglieder können mit Standardwerten (so genannten Rohwerten) gefüllt werden, die alle vom selben Typ sind. Der Rohwert für ein bestimmtes Aufzählungsmitglied ist immer derselbe. Rohwerte können Strings, Zeichen oder beliebige Ganzzahl- oder Gleitkommazahlentypen sein. Jeder Rohwert muss innerhalb seiner Aufzählungsdeklaration eindeutig sein. Wenn Ganzzahlen für Rohwerte verwendet werden, werden diese automatisch erhöht, wenn für einige Aufzählungsmitglieder kein Wert angegeben wird. Greifen Sie mit seiner toRaw-Methode auf den Rohwert eines Aufzählungsmitglieds zu.

2
SFX

Ich vermute, dass so etwas in Foundation in Enum-Optionen modelliert wird:

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

... wo myOptions und nextOptions vom Typ TestOptions sind - ich bin nicht genau sicher, wie fromMask() und getLogicValue() hier wirken sollen (ich habe nur ein paar Vermutungen angestellt), könnte vielleicht jemand dies aufgreifen und klären?

1
fqdn

Wenn Sie Bitfield in Swift wollen, ist die Enumeration der falsche Weg. Besser einfach so machen

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

Sie brauchen den Klassen-/statischen Wrapper nicht wirklich, aber ich füge ihn als eine Art Pseudo-Namespace bei.

1
John Henckel

Ich verwende Folgendes: Ich brauche die beiden Werte, die ich erhalten kann, rawValue für die Indexierung von Arrays und den Wert für Flags.

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
1
Peter Ahlberg

Führen Sie eine bitweise Operation mit einem Rohwert durch und erstellen Sie dann ein neues Enum-Objekt mit dem Ergebnis.

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

0
Vishun

Ich habe hier etwas zusammengestellt, um zu versuchen, ein schnelles Enum zu erstellen, das in gewissem Maße einem C # -Flag-Stil ähnelt. Aber ich lerne gerade Swift, daher sollte dies nur als "Proof of Concept" Code betrachtet werden.

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

Dies zeigt, wie es verwendet werden kann:

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

Verbesserungsvorschläge sind willkommen.

EDIT: Ich habe diese Technik selbst aufgegeben und kann sie daher offensichtlich nicht mehr empfehlen.

Das große Problem ist, dass Swift fordert, dass rawValue mit einem der definierten Aufzählungswerte übereinstimmen muss. Dies ist in Ordnung, wenn es nur 2 oder 3 oder vielleicht sogar 4 Flag-Bits gibt. Definieren Sie einfach alle Kombinationswerte, um Swift glücklich zu machen. Aber für 5 oder mehr Flaggenbits wird es total verrückt.

Ich werde dieses Posting hinterlassen, falls jemand es für nützlich hält oder vielleicht als Warnung, wie es NICHT zu tun ist.

Meine derzeitige Lösung für diese Situation basiert auf der Verwendung einer struct anstelle von enum zusammen mit einem Protokoll und einigen Erweiterungsmethoden. Das funktioniert viel besser. Vielleicht poste ich es eines Tages, wenn ich mir sicher bin, dass das nicht auch nicht nach hinten losgeht.

0
RenniePet

Das hat bei mir funktioniert.

  • 1 << 0 // 0000 
  • 1 << 1 // 0010 
  • 1 << 2 // 0100
  • 1 << 3 // 1000

      enum Collision: Int {
        case Enemy, Projectile, Debris, Ground
        func bitmask() -> UInt32 {
            return 1 << self.rawValue
          }
      }
    
0
codebendr