Declaring and using a bit field enum in Swift

前端 未结 14 1991
余生分开走
余生分开走 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:29

    Updated for Swift 2/3

    Since swift 2, a new solution has been added as "raw option set" (see: Documentation), which is essentially the same as my original response, but using structs that allow arbitrary values.

    This is the original question rewritten as an 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]
    

    Combining with new values can be done exactly as Set operations (thus the OptionSet part), .union, likewise:

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

    Same as doing One | Four in its C-equivalent. As for One & Mask != 0, can be specified as a non-empty intersection

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

    Weirdly enough, most of the C-style bitwise enums have been converted to their OptionSet equivalent on Swift 3, but Calendar.Compontents does away with a Set<Enum>:

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

    Whereas the original NSCalendarUnit was a bitwise enum. So both approaches are usable (thus the original response remains valid)

    Original Response

    I think the best thing to do, is to simply avoid the bitmask syntax until the Swift devs figure out a better way.

    Most of the times, the problem can be solved using an enum and and a Set

    enum Options
    {
        case A, B, C, D
    }
    
    var options = Set<Options>(arrayLiteral: .A, .D)
    

    An and check (options & .A) could be defined as:

    options.contains(.A)
    

    Or for multiple "flags" could be:

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

    Adding new flags (options |= .C):

    options.insert(.C)
    

    This also allows for using all the new stuff with enums: custom types, pattern matching with switch case, etc.

    Of course, it doesn't have the efficiency of bitwise operations, nor it would be compatible with low level things (like sending bluetooth commands), but it's useful for UI elements that the overhead of the UI outweighs the cost of the Set operations.

    0 讨论(0)
  • 2020-12-13 09:32

    @Mattt's very famous "NSHipster" has an extensive detailed description of the RawOptionsSetType : http://nshipster.com/rawoptionsettype/

    It includes a handy 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) }
        // ...
    }
    
    0 讨论(0)
  • 2020-12-13 09:35

    Do bitwise operation using raw value then create a new enum object using the result.

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

    0 讨论(0)
  • 2020-12-13 09:38

    If you want bitfield in Swift, then enum is the wrong way. Better just do like this

    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
    

    You don't really need the class/static wrapper, but I include it as a kind of pseudo namespace.

    0 讨论(0)
  • 2020-12-13 09:38

    Task

    Get all flags from flags_combination. Each flag and flags_combination are integers. flags_combination = flag_1 | flags_2

    Details

    • Xcode 11.2.1 (11B500), Swift 5.1

    Solution

    import Foundation
    
    protocol FlagPrototype: CaseIterable, RawRepresentable where RawValue == Int {}
    extension FlagPrototype {
        init?(rawValue: Int) {
            for flag in Self.allCases where flag.rawValue == rawValue {
                self = flag
                return
            }
            return nil
        }
        static func all(from combination: Int) -> [Self] {
            return Self.allCases.filter { return combination | $0.rawValue == combination }
        }
    }
    

    Usage

    enum Flag { case one, two, three }
    extension Flag: FlagPrototype {
        var rawValue: Int {
            switch self {
            case .one: return 0x1
            case .two: return 0x2
            case .three: return 0x4
            }
        }
    }
    
    var flags = Flag.two.rawValue | Flag.three.rawValue
    let selectedFlags = Flag.all(from: flags)
    print(selectedFlags)
    if selectedFlags == [.two, .three] { print("two | three") }
    
    0 讨论(0)
  • 2020-12-13 09:40

    This worked for me.

    • 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 讨论(0)
提交回复
热议问题