Swift bitmask, how to know if a int is contained in a bitmask?

151 Views Asked by At

I need to understand how to know if a bitmask value has an OptionSet value that comes from my API. My OptionSet is this:

struct RecordsObjectivesStates: OptionSet {
    var rawValue: Int
    init(rawValue: Int) {self.rawValue = rawValue}
    static let None                             = RecordsObjectivesStates(rawValue: 1 << 0)
    static let RecordRedeemed                   = RecordsObjectivesStates(rawValue: 1 << 1)
    static let RewardUnavailable                = RecordsObjectivesStates(rawValue: 1 << 2)
    static let ObjectiveNotCompleted            = RecordsObjectivesStates(rawValue: 1 << 4)
    static let Obscured                         = RecordsObjectivesStates(rawValue: 1 << 8)
    static let Invisible                        = RecordsObjectivesStates(rawValue: 1 << 16)
    static let EntitlementUnowned               = RecordsObjectivesStates(rawValue: 1 << 32)
    static let CanEquipTitle                    = RecordsObjectivesStates(rawValue: 1 << 64)
}

I have a response value of "20" from API. That is ObjectiveNotCompleted+Invisible. I want to know if Invisible is in my "20" value (and is it, obviously).

I found a lot of explanations about hexadecimal values or binary values, but nothing about Int values. Can anybody help me?

2

There are 2 best solutions below

0
HangarRash On BEST ANSWER

You state that the value 20 clearly contains the value invisible. But this is incorrect. You have defined invisible as 1 << 16. That is 65536. The value 20 certainly does not contain 65536.

So you first need to fix your OptionSet. You should also name the constants starting with lowercase letters.

struct RecordsObjectivesStates: OptionSet {
    var rawValue: Int

    init(rawValue: Int) {
        self.rawValue = rawValue
    }

    static let recordRedeemed        = RecordsObjectivesStates(rawValue: 1 << 0) // 1
    static let rewardUnavailable     = RecordsObjectivesStates(rawValue: 1 << 1) // 2
    static let objectiveNotCompleted = RecordsObjectivesStates(rawValue: 1 << 2) // 4
    static let obscured              = RecordsObjectivesStates(rawValue: 1 << 3) // 8
    static let invisible             = RecordsObjectivesStates(rawValue: 1 << 4) // 16
    static let entitlementUnowned    = RecordsObjectivesStates(rawValue: 1 << 5) // 32
    static let canEquipTitle         = RecordsObjectivesStates(rawValue: 1 << 6) // 64
}

Now with those changes (note the removal of None), the constants have the correct values. And now the value 20 does in fact contain invisible (16).

let states = RecordsObjectivesStates(rawValue: 20)

if states.contains(.invisible) {
    // do stuff
}

You do not need the none value because that's easily represented by the empty option set which is what you get with a raw value of 0.

0
Rob Napier On

As Geoff notes, << n means "shift left by n" which provides an easy way to define powers of two. You rarely would shift by powers of two. So the correct definition would be:

struct RecordsObjectivesStates: OptionSet {
    var rawValue: Int
    static let recordRedeemed        = RecordsObjectivesStates(rawValue: 1 << 0)
    static let rewardUnavailable     = RecordsObjectivesStates(rawValue: 1 << 1)
    static let objectiveNotCompleted = RecordsObjectivesStates(rawValue: 1 << 2)
    static let obscured              = RecordsObjectivesStates(rawValue: 1 << 3)
    static let invisible             = RecordsObjectivesStates(rawValue: 1 << 4)
    static let entitlementUnowned    = RecordsObjectivesStates(rawValue: 1 << 5)
    static let canEquipTitle         = RecordsObjectivesStates(rawValue: 1 << 6)

    // Generally shouldn't do this, but for demonstration
    static let none: RecordsObjectivesStates = []
}

I've provided .none because you did, but usually you wouldn't. You'd just use [] to express it. It's generally clearer that way.

With this, you can create a value with 20:

let value = RecordsObjectivesStates(rawValue: 20)

And check whether it contains particular values with .contains:

value.contains(.invisible)

A reason that you shouldn't generally define .none is that it allows code like value.contain(.none), which is always true because every set includes the empty set. This is confusing, but sometime people still find it useful to have the constant.