Swift Optional Chaining
Swift Optional Chaining is a very useful feature. Optionals are a type in Swift that wrap primitive types and are typically used for preventing null values. Optional Chaining is the process of chaining calls on optional values such that it handles the unwrapping gracefully and concisely without giving runtime errors. As the name says, it chains multiple queries together.
But the same thing is achievable using if let, guard let and implicit unwrapping too.
What’s the need for Optional Chaining then?
Let’s look at an example in Playground first without bringing Optional Chaining.
Without Optional Chaining
Let’s assume we have the following classes
import Foundation class University { var city: City? var universityName: String = "Indian Institute of Technology" } class College { var discplines = [Discpline?]() var numberOfStreams : Int { return discplines.count } } class City { var cityName: String? var college: College? } class Discpline { var discplineName: String? var student : Student? } class Student { var name: String? var discpline : Discpline init(currentDiscpline: Discpline) { self.discpline = currentDiscpline } func printDetails() ->String { return "Student Details:\n Name:\(name ?? "Na") \n Discpline: \(discpline.discplineName ?? "NA")" } }
Every class contains an instance of the below class. All instances are defined as optionals. College class holds an array of Discpline class objects.
Let’s instantiate each of these classes.
var university = University() var myCity = City() myCity.cityName = "Hyderabad" var myCollege = College() var csDiscpline = Discpline() csDiscpline.discplineName = "Computer Science" var meDiscpline = Discpline() meDiscpline.discplineName = "Electrical Engineer" myCollege.discplines = [csDiscpline,meDiscpline] var myStudent = Student(currentDiscpline: csDiscpline) myStudent.name = "Balu"
Using Implicit Unwrapping
Let’s get and retrieve properties and functions from the classes using implicit unwrapping (!).
university.city = myCity university.city!.college = myCollege university.city!.college!.discplines = [csDiscpline,meDiscpline] university.city!.college!.discplines[0]!.student = myStudent var finalString = university.city!.college!.discplines[0]!.student!.printDetails()
This looks okay. But we know from the Optionals in Swift tutorial that implicit unwrapping can lead to runtime crashes since it unwraps the optional without checking if it’s nil or not.
In the above code, if we set myStudent as nil, it’ll lead to a CRASH.
university.city!.college!.discplines[0]!.student = nil var finalString = university.city!.college!.discplines[0]!.student!.printDetails() . //crash since student is nil
Let’s try using if let/if var
if var uCity = university.city { uCity = myCity if var uCollege = uCity.college { uCollege = myCollege uCollege.discplines = [csDiscpline,meDiscpline] if let uDiscpline = uCollege.discplines[0] { if var uStudent = uDiscpline.student { uStudent = myStudent print(uStudent.printDetails()) } } } }
Too much of nesting! And that too just to set values and print the result in a very basic code with hardly a few properties in each class. This is inefficient.
Using guard var
Let’s do the same using guard var.
func usingGuard() { guard var uCity = university.city else { print("City is nil") return } uCity = myCity guard var uCollege = uCity.college else { print("College is nil") return } uCollege = myCollege uCollege.discplines = [csDiscpline,meDiscpline] guard let uDiscpline = uCollege.discplines[0] else { print("Discpline is nil") return } guard var uStudent = uDiscpline.student else { print("Student is nil") return } uStudent = myStudent print(uStudent.printDetails()) }
This is better than if var but still too many conditional checks. The implicit wrapping was concise and crisp but dangerous since it didn’t check the optional value before unwrapping.
This is where Optional Chaining comes to our rescue.
It’s an alternative and better form of forced unwrapping. In optional chaining, we just need to replace ! with ?
How does Optional Chaining work
Optional Chaining is done over optional values. It returns the desired value wrapped as an Optional. If the optional is nil, it returns an Optional(nil). Optional Chaining always gives you optional thereby eliminating the chance of runtime crashes.
Hence two things:
- If the type that’s been retrieved through Optional Chaining is not optional, then after the optional chaining is done, it becomes optional.
- If the type was already optional, it’ll stay optional only. It’ll not get nested like Optional(Optional(String)).
Implementation With Optional Chaining
The below code has optional chaining implemented.
Now printDetails() was defined to return a String. Does it? NO.
It returns an Optional(String) since the Optional Chaining wraps the returned value with an optional.
Accessing Subscript calls through optional chaining
Change the Discpline class to
class College { var discplines = [Discpline?]() var numberOfStreams : Int { return discplines.count } subscript(i: Int) -> Discpline? { get { return discplines[i] } set { discplines[i] = newValue } } }
Thanks to subscripts we can get rid of the discpline object invocation.
if let discpline0 = university.city?.college?[0]?.discplineName { print(discpline0) }
Note: When accessing a subscript on an optional value through optional chaining, you place the question mark before the subscript’s brackets, not after. Hence we’d done college?[0]. It gets the first discipline.