Swift, more elegant code: OptionSet

Ahmad Fayyas
5 min readJun 25, 2018

--

Would you like to know how to deal with Bitmasks in Swift? Have you ever heard of OptionSet it and you want to know more? Well, this is for you!

What is OptionSet?

OptionSet is a Protocol that conforming to makes you able to do bitmasking. It is a representation of a bit set, each single bit would represent an option. Sounds unclear? Let’s code:

How to implement OptionSet?

Here is a basic implementation for a Structure (MyStruct) that conforms to the OptionSet :

struct MyStruct: OptionSet {
let rawValue: Int
static let firstOption = MyStruct(rawValue: 1 << 0)
static let secondOption = MyStruct(rawValue: 1 << 1)
static let thirdOption = MyStruct(rawValue: 1 << 2)
}

Let’s explain what happened in the above code:

Conforming to OptionSet requires declaring rawValue as any type of FixedWidthInteger, such as Int, Int8, or Int16. Next, you would need to declare the desired options statically, That’s all to create your OptionSet!

“Why does it have to be static? And what is the << thing?”

Sidebar Note: Before going straightforward to the OptionSet options declaration, I would suggest to get familiar with the “zero one” world — if you are not so far. Understanding the binary representation of a decimal value would make it easier to understand and implement the OptionSet options. Here is a good article: Bits, Bytes, Building With Binary.

Logically, the option should be declared as static, because you would access it directly without the need to instantiate from a conformed structure (MyStruct in our example) — as we’ll see in a moment. Next, the option should be declared as an instance of the conformed structure (MyStruct in our example) with assigning a desired “Binary counting” rawValue, and that’s the purpose of using the << bitwise left shift operator.

Although I would recommend checking the documentation for Bitwise Operator — Bitwise Left and Right Shift Operators, I would mention a quick brief of how it works: it moves all bits in a number to the left, for instance:

let one = 0b0001 // 1
let shiftedZero = one << 2 // 4 (0b0100)
let twentyOne = 0b0010101 // 21
let shiftedTewntyOne = twentyOne << 2 // 84 (1010100)

we just shifted the bits in one and twentyOne two steps. In other words, it added two zeros on the right side of the value.

This means that implementing the options:

static let firstOption = MyStruct(rawValue: 1 << 0) // 1
static let secondOption = MyStruct(rawValue: 1 << 1) // 10
static let thirdOption = MyStruct(rawValue: 1 << 2) // 100

is identical to:

static let firstOption = MyStruct(rawValue: 1)
static let secondOption = MyStruct(rawValue: 2)
static let thirdOption = MyStruct(rawValue: 4)

“So, why we are setting the rawValue by using << operator?”

It could be more convenient to ensure following the binary counting formula (1, 2, 4, 8, 16…). Setting a raw value that does not follow the binary counting formula would cause a logical error! It doesn’t mean that it reproduces any crashes or even a compile-time error, but you won’t get the benefit (purpose) of declaring an OptionSet. For example, setting the “3” as a rawValue for one of the declared options would be a conflict with the combination of the “1” and “2” options.

Why to use OptionSet?

You could simply consider it as a combination of a finite number of options represented in a single value.

Note that we are not talking about collections that could contain multiple elements, such as an array, it is just one value).

Example:

There is a requirement to let the user choose his favorite pet, it should be one of the following options (pussycat, puppy, hamster, or chameleon). The first “make sense” comes to mind option is implementing an enum:

enum Pet {
case pussycat, puppy, hamster, chameleon
}
let favortiePet: Pet = .puppy

However, things change vastly 😑!

Our requirement now is that the user can choose multiple favorite pets…

As a workaround, it can be updated to be declared as a proper collection type (Array or maybe Set):

let favortiePets: [Pet] = [.pussycat, .puppy]

It’s legal and “not that bad”, nevertheless it can be optimized!

Well, let’s see how to handle it using OptionSet:

struct Pet: OptionSet {
let rawValue: UInt8
static let pussycat = Pet(rawValue: 1 << 0)
static let puppy = Pet(rawValue: 1 << 1)
static let hamster = Pet(rawValue: 1 << 2)
static let chameleon = Pet(rawValue: 1 << 3)
}
let singlePet: Pet = .puppy
let multiplePets: Pet = [.puppy, .pussycat]

Note that multiplePets type is Pet , NOT [Pet] (don’t get tricked by seeing the square brackets 👻).

The rawValue of:

  • singlePet is 2 (1 << 1).
  • multiplePets is 3 (1 << 1 + 1 << 0).

Implies that:

  • pussycat: rawValue = 1
  • hamster and chameleon: rawValue = 12 (4 + 8)
  • ALL pets: rawValue = 15 (1 + 2 + 4 + 8)

Furthermore

Another pretty cool feature that you will get when using OptionSet is the set-related operations, for instance (based on Pet struct):

let options1: Pet = [.pussycat, .puppy]
let options2: Pet = [.puppy, .hamster, .chameleon]
let intersection = options1.intersection(options2)
print(intersection) // Pet(rawValue: 2) 👉 puppy
let union = options1.union(options2)
print(union) // Pet(rawValue: 15) 👉 pussycat, puppy, hamster & chameleon
let subtracting = options1.subtracting(options2)
print(subtracting) // Pet(rawValue: 1) 👉 pussycat
let contains = options1.contains(.hamster)
print(contains) // false

Wrapping Up:

OptionSet is the way to achieve bitmasks. As we’ve taken a look, it could be a good choice for storing a finite list of related options as a single value. However, keep in mind to use it wisely! In the end, it is not a collection.

Reference:

Author ✍️

Appreciate your feedback 👏 Stay tuned for more “Swift, more elegant code” topics.

Thanks for Reading!

--

--

Ahmad Fayyas

Software Engineer. If I’m not in front of my pc coding or playing video games, you can find me hanging out with family and friends, or sleeping!