Declaring and using a bit field enum in Swift

前端 未结 14 2016
余生分开走
余生分开走 2020-12-13 09:24

How should bit fields be declared and used in Swift?

Declaring an enum like this does work, but trying to OR 2 values together fails to compile:

enum         


        
14条回答
  •  半阙折子戏
    2020-12-13 09:43

    Here's something I put together to try to make a Swift enum that resembles to some extent a C# flags-style enum. But I'm just learning Swift, so this should only be considered to be "proof of concept" code.

    /// 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(_ flagToTest : T) -> Bool {
          return (self.rawValue & flagToTest.rawValue) != 0
       }
    }
    

    This shows how it can be used:

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

    Suggestions for improvement are welcome.

    EDIT: I've given up on this technique myself, and therefore obviously can't recommend it anymore.

    The big problem is that Swift demands that rawValue must match one of the defined enum values. This is OK if there are only 2 or 3 or maybe even 4 flag bits - just define all of the combination values in order to make Swift happy. But for 5 or more flag bits it becomes totally crazy.

    I'll leave this posted in case someone finds it useful, or maybe as a warning of how NOT to do it.

    My current solution to this situation is based on using a struct instead of enum, together with a protocol and some extension methods. This works much better. Maybe I'll post it someday when I'm more sure that that isn't also isn't going to backfire on me.

提交回复
热议问题