Search code examples
enumsswiftbit-fields

Declaring and using a bit field enum in Swift


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

I looked at how Swift imports Foundation enum types, and it does so by defining a struct that conforms to the RawOptionSet protocol:

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

And the RawOptionSet protocol is:

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

However, there is no documentation on this protocol and I can't figure out how to implement it myself. Moreover, it's not clear if this is the official Swift way of implementing bit fields or if this is only how the Objective-C bridge represents them.


Solution

  • 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.