Optionals are the keystone in the Swift Cathedral of Safety. They protect against the possibility of a value being absent, the program looking for that value and then crashing. Welcome to a guide through 6 useful tools used to navigate the landscape of question marks, exclamations marks (and even double question marks??)!
When a value is optional it means that it can be in one of two states: 1) it contains something or 2) it contains nil. In fact optionals in Swift are enums with two cases: .None, or .Some<t>. Optionals are a distinct type and the compiler wont let them be used until the value has been “unwrapped”.
Here is the result of improperly using a value that contains nil:
When designing the data models in your app. Ask yourself: must this variable or constant have a value for the program to function as intended?
1) Optional Unwrapping
The keywords “if let” are a common sight in Swift. The if let statement provides a safe way to check whether the optional contains a value, if it does the value is assigned to a temporary constant that can be used inside the code block. This is known as “optional binding”. If the optional returns nil the else statement will be executed. Either way the user carries on his merry way and no-one’s trousers have caught fire.
In the example below chartPosition, stageName and debutAlbum have all been declared as optionals. This is because it would be possible to have an album that doesn’t make it onto the charts, the artist might not have a stage name and they make not have released a debut album yet.
struct Album { let title: String let yearReleased: Int let chartPosition: Int? } struct RockStar { let birthName: String let stageName: String? let yearOfBirth: Int let band: String let role: String let debutAlbum: Album? } let queenDebut = Album(title: "Queen", yearReleased: 1973, chartPosition: 24) let freddie = RockStar(birthName: "Farrokh Bulsara", stageName: "Freddie Mercury", yearOfBirth: 1946, band: "Queen", role: "lead singer", debutAlbum: queenDebut) let roger = RockStar(birthName: "Roger Taylor", stageName: nil, yearOfBirth: 1949, band: "Queen", role: "drummer", debutAlbum: queenDebut) // Optional binding with "if let" if let stageName = freddie.stageName { print("Some rock stars have cool stage names like \(stageName)") } else { print("Not all rock stars use stage names") }
2) Optional Chaining
It can become messy when there are nested levels of optional values. Optional values that depend on other optional values can be dealt with using optional chaining, like so:
if let album = freddie.debutAlbum?.title { // now safely use temporary constant within scope of code block print(album) // "Queen" (...yes it's a self titled album) }
3) Guard
Guard was introduced in Swift 2.0 because people hate pyramids… not pyramids of the King Tut persuasion but code such as the “pyramid of doom” shown below:
struct BrassSection { let trumpets: Int? let trombones: Int? let frenchHorns: Int? let tubas: Int? } func allBrassInstruments(band: BrassSection) { if let trumpets = band.trumpets { // there are trumpets if let trombones = band.trombones { // there are trombones if let frenchHorns = band.frenchHorns { // there are French horns if let tubas = band.tubas { // there are tubas. So: print("This brass section has 4 kinds of instrument: \(trumpets) trumpets, \(frenchHorns) French horns, \(trombones) trombones and \(tubas) tubas") } else { print("The section has no tubas") } } else { print("The section has no French horns") } } else { print("The section has no trombones") } } else { print("The section has no trumpets") } }
Complete madness of nested if let statements where it’s particularly hard to follow the flow of control through the potential else clauses. As confusing as it is in this simple (slightly abstract) example things can get out of hand in a big way when dealing with network requests and handling JSON data.
Here’s what the same code looks like using guard:
func allBrassInstruments(band: BrassSection) { guard let trumpets = band.trumpets else { print("The section has no trumpets") return } guard let frenchHorns = band.frenchHorns else { print("The section has no French horns") return } guard let trombones = band.trombones else { print("The section has no trombones") return } guard let tubas = band.tubas else { print("The section has no tubas") return } print("This brass section has 4 kinds of instrument: \(trumpets) trumpets, \(frenchHorns) French horns, \(trombones) trombones and \(tubas) tubas") }
Note: the return keyword is used to leave the scope of the function if one of the optionals is found to contain nil.
The guard statement always includes an else clause which is executed if the condition is not met, i.e. the optional value contains nil. If the optional does contain .Some value the code after the guard block now becomes the happy path and the variable is available for use. This is unlike using if let optional binding where the value is only available as a temporary constant within the if statement.
Here’s another use of guard continuing on with the Rock Star example:
// create variable with current year let date = NSDate() let calendar = NSCalendar.currentCalendar() let components = calendar.components([.Day , .Month , .Year], fromDate: date) let currentYear = components.year // define error cases enum AlbumYearError: ErrorType { case YearNotProvided case YearTooEarly } // Without guard - behold the pyramid func howOldIsAlbum(albumYear: Int?) throws { if let albumYear = albumYear { if albumYear > 1948 { print("The recording is \(currentYear - albumYear) years old") } else { throw AlbumYearError.YearTooEarly } } else { throw AlbumYearError.YearTooEarly } }
Now using guard:
func howOldIsAlbum(albumYear: Int?) throws { guard let albumYear = albumYear else { throw AlbumYearError.YearNotProvided } guard albumYear > 1948 else { throw AlbumYearError.YearTooEarly } print("The recording is \(currentYear - albumYear) years old") } // implementation do { try howOldIsAlbum(freddie.debutAlbum?.yearReleased) } catch AlbumYearError.YearNotProvided { print("No album year was provided") } catch AlbumYearError.YearTooEarly { print("Year provided is before albums were recorded") } // The result being: "The recording is 43 years old"
4) Forced Unwrapping!
If it’s certain that the optional value definitely doesn’t contain nil the optional binding process can be bypassed. Instead the value can be force-unwrapped with a “!”. This says “go ahead and catapult the watermelon because I’m sure it’s not returning to my face as nil!” For example:
// this extends the RockStar struct extension RockStar { func description() -> String { if stageName != nil { return "\(stageName!) is known as the \(role) for \(band)" } else { return "\(birthName) is known as the \(role) for \(band)" } } } roger.description() // "Roger Taylor is known as the drummer for Queen" freddie.description() // "Freddie Mercury is known as the lead singer for Queen"
As we’ve checked stageName is not nil we can force unwrap it in the subsequent code block. Note: if it wasn’t force unwrapped it would return Optional(stageName).
! is also known jokingly as the “crash operator” because if optionals are carelessly force-unwrapped they can cause an app to crash.
5) Implicitly Unwrapped Optionals
There are certain cases where it’s clear that an optional value will exist immediately after it has been defined. In other words it will never return nil. Instead of unwrapping this value everywhere it’s used in the code (either with forced unwrapping or optional binding) it’s cleaner to declare it as an implicitly unwrapped optional by adding an exclamation mark after the type.
If a value is declared as an implicitly unwrapped optional it means that Swift wont check for you whether the value contains something or nothing. Once again you are saying “Lords of Runtime: I do not fear death. Unleash the mighty watermelon catapult for this value will ney contain nil!”
A prime example of this is when connecting visual elements from the storyboard using IBOutlet. Even though, for example, the UITextField definitely exists it wont be created until needed. It would be a pain to litter the code with tons of if let statements when it’s known that this objects will be created, hence it’s acceptable to declare it as an implicitly unwrapped optional.
@IBOutlet weak var nameTextField: UITextField!
6) Nil Coalescing Operator
While the nil coalescing operator sounds like a part Doc Brown needs to send the DeLorean back to 1985, it’s actually quite simple. The double question mark syntax is short hand way to unwrap an optional and either assign it to a variable/constant or assign it a default value.
In the example below a new rock star has been created. A young flute playing virtuoso by the name of John Smith. His stage name is currently set to nil. The nil coalescing operation can be used to check whether the optional type stageName contains a value. If it does this would be assigned to the newStageName variable, but because John’s stage name contains nil the value after the ?? (“TBD”) is assigned to newStageName.
let newRockStar = RockStar(birthName: "John Smith", stageName: nil, yearOfBirth: 2014, band: "The Johns", role: "flautist", debutAlbum: nil) let newStageName = newRockStar.stageName ?? "TBD" newStageName // "TBD" // can also use ?? without assigning to a variable. For example: print("John Smiths stage name is \(newRockStar.stageName ?? "TBD")")
It’s really syntactic sugar on a ternary conditional operator (which is definitely a part in the flux capacitor!)
Code for these examples can be found on GitHub here!