A surprising number of developers struggle with bitmasks and bitwise operations. If you've had problems working with bitmasks, then I have good news for you. Swift's standard library makes this easy. In this episode, I walk you through an example I worked on last week for an application I'm working on.
OptionSet Protocol
The application I'm currently working on has the ability to schedule reminders. The feature is similar to that of Apple's Clock application. A reminder or alarm can be scheduled on a specific day, but it can also be repeated on other days. A bitmask is the logical solution to solve this problem as I want to store the schedule for a reminder as a single value.
Let me show you how easy and elegant Swift makes the implementation. Open Xcode and create a new playground.
The protocol we are interested in is the OptionSet
protocol. As the name of the protocol implies, an OptionSet
instance represents a set of options. This is exactly what we need for a bitmask.
I have named the type we are creating Schedule
. Notice that it is a structure that conforms to the OptionSet
protocol.
import Foundation
struct Schedule: OptionSet {
}
The compiler immediately warns us that Schedule
doesn't conform to the RawRepresentable
protocol. What is that about? Press Command and click OptionSet
to navigate to the declaration of the OptionSet
protocol. The protocol declaration shows us that OptionSet
inherits from the RawRepresentable
protocol.
public protocol OptionSet : RawRepresentable, SetAlgebra {
associatedtype Element = Self
init(rawValue: Self.RawValue)
}
This means the Schedule
structure also needs to conform to the RawRepresentable
protocol. This is easy enough, though. We declare a constant property, rawValue
, of type Int
.
import Foundation
struct Schedule: OptionSet {
// MARK: - Properties
let rawValue: Int
}
As a result, we get an initializer for free, init(rawValue:)
.
Creating an Option Set
We have defined an option set, but how do we work with instances of the Schedule
structure? The Swift standard library comes to the rescue once again. For each option, we declare a static member. Notice that we use bitwise operators to define the raw value for each static member. Each static member has a unique value. That is key.
import Foundation
struct Schedule: OptionSet {
// MARK: - Properties
let rawValue: Int
// MARK: - Options
static let monday = Schedule(rawValue: 1 << 0)
static let tuesday = Schedule(rawValue: 1 << 1)
static let wednesday = Schedule(rawValue: 1 << 2)
static let thursday = Schedule(rawValue: 1 << 3)
static let friday = Schedule(rawValue: 1 << 4)
static let saturday = Schedule(rawValue: 1 << 5)
static let sunday = Schedule(rawValue: 1 << 6)
}
Using this approach, the syntax may remind you of working with enumerations.
let schedule = Schedule.monday
Or
let schedule: Schedule = .monday
Working With Options Sets
Working with options sets is easy and intuitive. Take a look at the example below in which we assign the static member wednesday
to a variable.
let schedule = Schedule.wednesday
Or
let schedule: Schedule = .wednesday
You can use an array to create an option set with one or more static members. It doesn't get easier than this. If you have worked with bitmasks in Objective-C, then I am sure you agree Swift's implementation is easier to work with, less confusing, and more intuitive.
let weekend = [Schedule.saturday, Schedule.sunday]
Or
let weekend: Schedule = [.saturday, .sunday]
You can also use the initializer of the Schedule
struct to create an option set. This is especially useful if you've stored the raw value in, for example, a database.
let schedule = Schedule(rawValue: 127)
The rawValue
property provides access to the raw value of the option set.
schedule.rawValue
Manipulating Options Sets
Creating option sets is straightforward. It doesn't stop here, though. The OptionSet
protocol defines a collection of methods that make working with option sets easy and powerful. Take a look at this example in which we combine two option sets.
let schedule1: Schedule = .monday
let schedule2: Schedule = .tuesday
let union = schedule1.union(schedule2)
You can also ask an option set whether it contains a particular element.
let weekend: Schedule = [.saturday, .sunday]
if weekend.contains(.friday) {
print("The schedule contains Friday.")
} else {
print("The schedule doesn't contain Friday.")
}
You can use insert(_:)
and remove(_:)
to insert and remove members from an existing option set.
var schedule: Schedule = []
schedule.insert(.monday)
schedule.remove(.thursday)
An empty option set is represented by two square brackets.
var schedule: Schedule = []
One More Trick
You can also define static members for convenience. The example we have created in this episode makes it easy to work with schedules. We can define a few additional static members to make this even easier, more convenient.
The weekend
static member defines an option set that contains the values stored in saturday
and sunday
. The weekdays
static member only contains the days of the week.
import Foundation
struct Schedule: OptionSet {
// MARK: - Properties
let rawValue: Int
// MARK: - Options
static let monday = Schedule(rawValue: 1 << 0)
static let tuesday = Schedule(rawValue: 1 << 1)
static let wednesday = Schedule(rawValue: 1 << 2)
static let thursday = Schedule(rawValue: 1 << 3)
static let friday = Schedule(rawValue: 1 << 4)
static let saturday = Schedule(rawValue: 1 << 5)
static let sunday = Schedule(rawValue: 1 << 6)
static let weekend: Schedule = [.saturday, .sunday]
static let weekdays: Schedule = [.monday, .tuesday, .wednesday, .thursday, .friday]
}
What's Next?
The OptionSet
protocol is a wonderful member of Swift's standard library. Working with bitmasks doesn't have to be complex or cumbersome. Later this week, I show you how to create a basic user interface that allows users to define a schedule using the Schedule
struct.