Categories
Swift

Using OptionSet

Let’s take an example. You are working as a team, with 4 members (Alex, John, Michael, Henry). The team is working on 4 iOS projects (Calculator, Tetris, Streaming App, Social Network)

Now we want to mark which team member is working on which project.

A simple way would be to use enum for this.

enum Project {
     case calculator
     case videoGame
     case streamingApp
     case socialMedia
 }
 

 let alex:    Project = .calculator
 let john:    Project = .videoGame
 let michael: Project = .streamingApp
 let henry:   Project = .socialMedia 

One problem here is that we assign only one project for one employee. If we would need to be able to have employees working on several projects at the same time, how could we map this then?

Another problem is that we can specify if an employee works on nothing. For this, we would need to use an optional, which isn’t too ideal

let alex:    [Project] = [.calculator, .streamingApp]
let john:    [Project] = []
let michael: [Project] = [.streamingApp, .socialMedia, .videoGame]
let henry:   [Project] = [.socialMedia] 

Here then we would need to use an Array. Problem is that items in the array are not necessarily unique, so nothing would prevent me from adding multiple times the same item in an array. What about using a Set then?

let alex:    Set<Project> = [.calculator, .streamingApp]
let john:    Set<Project> = []
let michael: Set<Project> = [.streamingApp, .socialMedia, .videoGame]
let henry:   Set<Project> = [.socialMedia] 

This works! Though we are now depending on the Set structure, which we can’t extend or can’t add our logic inside if needed.

To manage this, we want to rewrite the logic using OptionSet

Implementing OptionSet

To implement an OptionSet, you need just a few step

  • Implement protocol OptionSet
  • Specify RawValue (UInt8 or UInt16) to represent the value (used as binary)
  • Specify each cases as static members
  • In fact values will be represented as Binary values. You can check more online for more details on binary values, I’m not going to explain this here.
 struct Project: OptionSet {
     
     let rawValue: Int8
     
     static let calculator   = Project(rawValue: 1 << 0)
     static let videoGame    = Project(rawValue: 1 << 1) 
     static let streamingApp = Project(rawValue: 1 << 2)
     static let socialMedia  = Project(rawValue: 1 << 3) 
 }

 let alex:    Project = [.calculator, .streamingApp]
 let john:    Project = []
 let michael: Project = [.streamingApp, .socialMedia, .videoGame]
 let henry:   Project = .socialMedia 
  • Note: We do not need to use Array or Set here, but a plain type
  • Note: We can specify an empty case, like for john
  • Note: We can specify a unique value, like for henry

Using predefined values

One other advantage of using OptionSet is that you can specify some predefined values, which will be the same type as your object, but representing a specific concept.

struct Project: OptionSet {
     
     let rawValue: Int8
     
     static let calculator   = Project(rawValue: 1 << 0)
     static let videoGame    = Project(rawValue: 1 << 1)
     static let streamingApp = Project(rawValue: 1 << 2)
     static let socialMedia  = Project(rawValue: 1 << 3)
     
     static let allProject: Project = [.calculator, .videoGame, .streamingApp, .socialMedia]
     static let iOS:        Project = [.calculator, .videoGame, .socialMedia]
     static let android:    Project = [.videoGame, .streamingApp]
 } 

Using Set-related operations

It’s a very cool feature that you can use directly for free, for example:

// intersection
print("alex and michael have worked together on: '\(alex.intersection(michael))'")
// alex and michael have worked together on: 'Project(rawValue: 4)'

// subtracting
print("alex and michael have NOT worked together on: '\(alex.subtracting(michael))'")
// alex and michael have NOT worked together on: 'Project(rawValue: 1)

// union
print("All projects michael and henry worked on: '\(michael.union(henry))'")
// All projects michael and henry worked on: 'Project(rawValue: 14)'
 
// contains
print("Did michael work for streamingApp? '\(michael.contains(.streamingApp))'")
// Did michael work for streamingApp? 'true'

// equal
print("Did alex work only for calculator and streamingapp? '\(alex == [.calculator, .calculator])'")
// Did alex work only for calculator and streamingapp? 'false'
 
// isDisjoint
print("Did alex work on android? '\(!alex.isDisjoint(with: .android))'")
print("Did henry work on android? '\(!henry.isDisjoint(with: .android))'")
// Did alex work on android? 'true'
// Did henry work on android? 'false' 

Thank you for reading until here…  👊 Have a good week end…  🍺