This article covers optional chaining, one of the mechanisms built into Swift to enable us to safely work with optionals. Optional chaining can be used to manipulate an optional in a number of ways, including setting and retrieving the value of a property of the wrapped instance, setting and retrieving a value from a subscript on the wrapped instance, and calling a method on the wrapped instance. Optional chains fail gracefully when they do, which is great for working safely with optionals, but it also means that it is important to know what to expect when using optional chains to be able to use them effectively.
Related articles:
Contents
Uses of optional chaining
1. Setting and retrieving the value of a property of the wrapped instance
2. Setting and retrieving a value from a subscript on the wrapped instance
3. Calling a method on the wrapped instance
Optional chains fail gracefully
1. Failure of an optional chain to retrieve or return a value
2. Failure of an optional chain to call a method that does not return a value
3. Failure of an optional chain to set a value
Optionals wrapping optionals
Conclusion
What is optional chaining
Optional
is a special Swift type, instances of which may or may not wrap an instance of a given Swift type. Since an optional is an instance of the Optional
type and not an instance of the type wrapped by the optional, we cannot directly use an optional to set or retrieve the value of a property of the wrapped instance, set or retrieve a value from a subscript on the wrapped instance, or call a method on the wrapped instance. One way to do so would be to unwrap the optional, but it is easier and more convenient in many contexts to work directly with the wrapped instance. This is where optional chaining is used, which allows us to use the wrapped instance without having to unwrap the optional.
When creating an optional chain, we use the normal dot syntax and square brackets (in case of subscripts) to set or retrieve values, and call methods. We just insert a question mark (?
) immediately after any optional that appears in the chain. This enables us to reach inside the optional and directly use the wrapped instance.
Uses of optional chaining
We can use an optional chain to manipulate the instance wrapped by an optional in a number of ways, which we will demonstrate using a simple example involving people and their pets.
Let’s start by defining a protocol to model pets, with an optional property for the name of the pet, and a method to enable the pet to make its signature sound.
protocol Pet {
var name: String? { get set }
func makeSound()
}
Next, we define a class to model cats, which conforms to the Pet
protocol.
class Cat: Pet {
var name: String?
init(named name: String? = nil) {
self.name = name
}
func makeSound() {
print("Meow")
}
}
Finally, we define a class to model a person.
class Person {
var name: String
var pets: [Pet]?
init?(named name: String) {
guard !name.isEmpty else { return nil }
self.name = name
}
}
Note that the pets
property in Person
is an optional, which means a person may or may not have one or more pets. Moreover, the Person
class has a failable initializer, which prevents us from creating a person with an empty string as the name.
We will use the above setup to demonstrate the ways in which optional chaining can be used to manipulate optionals.
1. Setting and retrieving the value of a property of the wrapped instance
Let’s say we want to create a person named James, who has a cat named Bella. The first step is to create the required Person
and Cat
instances.
let person = Person(named: "James")
let pet = Cat(named: "Bella")
However, if we try to set the value of the pets
property of person
, we get an error.
person.pets = [pet]
// Error: Value of optional type 'Person?' must be unwrapped to refer to member 'pets' of wrapped base type 'Person'
This is because the initializer of Person
is failable, and it gives us an optional wrapping a person, which is not an instance of type Person
but rather an instance of type Optional<Person>
(or Person?
), as we can see below.
print(type(of: person)) // Optional<Person>
We can, however, work directly with the wrapped instance using an optional chain. We can do this quite simply by modifying the above assignment statement, inserting a question mark immediately after the optional.
person?.pets = [pet]
Since an optional chain is just a normal dot syntax chain with the additional question mark after any optional that may be present in the chain, we can make our optional chains as long as required. For instance, if James gets another cat, we can use the following optional chain to add a new cat to the pets
property of person
.
let newCat = Cat()
person?.pets?.append(newCat)
We can also use an optional chain to retrieve the value of a property of the wrapped instance. Here is how, for instance, we can retrieve the array of pets owned by James.
let personsPets = person?.pets
It is important to note that any value obtained using an optional chain is always an optional. In the above case, we should get an optional array of pets, which has the type Optional<Array<Pet>>
(or [Pet]?
). We check the type of personsPets
to confirm this and also see that, since James has two pets, personsPets
does have a wrapped value and so is not equal to nil
.
print(type(of: personsPets)) // Optional<Array<Pet>>
print(personsPets == nil) // false
2. Setting and retrieving a value from a subscript on the wrapped instance
We can use an optional chain to set a value from a subscript on the wrapped instance. Continuing our running example, let’s say James decides to name his second cat. We can do this using an optional chain, as shown below.
person?.pets?[1].name = "Whiskers"
We can also use an optional chain to retrieve a value from a subscript on the wrapped instance. Here is how we retrieve the name we just set.
var petName = person?.pets?[1].name
Since a value retrieved using an optional chain is always an optional, we can confirm that the name of the cat was correctly set, and retrieved, by printing the string wrapped by petName
using the nil
coalescing operator.
print(petName ?? "No pet name found") // Whiskers
It is important to note that, when using an optional chain to set or retrieve a value from a subscript on the wrapped instance, the question mark is inserted before the square brackets, as we have done in the above examples. This is because, in an optional chain, a question mark must be placed immediately after the name of any optional in the chain, which would be before the dot in case we are accessing a property or calling a method on the wrapped instance, and before the square brackets in case we are using a subscript on the wrapped instance.
We would place the question mark after the square brackets in an optional chain in cases where the instance on which we are using a subscript to retrieve a value is not an optional, but the value thus retrieved, which we want to use to set or retrieve the value of a property, or call a method, is an optional. A common example of this is using an optional chain to retrieve a value from a non-optional array holding optional values, and set or retrieve the value of a property, or call a method, on the retrieved optional value. To demonstrate this, we create an array of persons, using the Person
class we have been using.
let names = ["Sandra", "Kelly", "Jacob"]
let persons = names.map(Person.init)
Since Person
has a failable initializer, we get an array of optional persons, which has the type Array<Optional<Person>>
(or [Person?]
).
print(type(of: persons)) // Array<Optional<Person>>
Let’s say we want to assign a value to the pets
property of the first element of this array. In this case, the array we are accessing is non-optional so we don’t need to insert a question mark before the square brackets. However, since the elements of the array are optional, we do need to use a question mark immediately after the square brackets.
let cat = Cat()
persons[0]?.pets = [cat]
To see the difference more clearly, we can use both forms in the same optional chain. Shown below is how we would set a value for the name
property of the above cat.
persons[0]?.pets?[0].name = "Fluffy"
For anyone who may be a bit unsure about what is happening in the above optional chain, it may be useful to step through what the optional chain does. First, we use a subscript on the non-optional persons
array in the normal manner. This gets us the first element of the array, which is an optional wrapping a Person
instance. To access the pets
property of the wrapped Person
instance, we insert a question mark immediately after the square brackets. The pets
property in turn holds an optional wrapping an array of Pet
instances. To use a subscript on this wrapped array, we place the question mark before the square brackets, enabling us to get the first element of the array, and set its name property to the desired value.
Just to complete the example, we can use a similar optional chain to retrieve the pet name we have just set. We store the optional string we get from the optional chain in the petName
variable we had defined earlier, and use a nil
coalescing operator to print it.
petName = persons[0]?.pets?[0].name
print(petName ?? "No pet name found") // Fluffy
Longer optional chains are great for writing compact code, but they can sometimes hinder readability. It may be useful, therefore, to break up a longer optional chain where appropriate into easily readable chunks, to keep the code clear and intention-revealing.
3. Calling a method on the wrapped instance
We can use an optional chain to call a method on the instance wrapped by an optional. The Pet
type we have been using in our running example has a makeSound()
method, which prints the pet’s signature sound, as implemented by the conforming class.
Le’s use an optional chain to get the first pet owned by the person we defined earlier to make a sound.
person?.pets?[0].makeSound() // Meow
Since the person owns two cats, we can get them to meow in unison.
person?.pets?.forEach() { $0.makeSound() }
// Meow
// Meow
We can also use an optional chain to call a method that returns a value. Just as with a value retrieved using an optional chain from a property or a subscript on the wrapped instance, we always get an optional when we use an optional chain to call a method on the wrapped instance, regardless of whether the return value of the method is optional.
Optional chains fail gracefully
Optional chains are one of the major ways in which we can use Swift optionals in a safe manner. This is because an optional chain never tries to force-unwrap an optional. It instead reaches into the optional and checks whether the optional contains a wrapped instance. If a wrapped instance is found, the optional chain uses it for the intended purpose. If, on the other hand, there is no wrapped instance, or in other words nil
is encountered, the optional chain fails gracefully, i.e., it does not cause an error. It is noteworthy that an optional chain with more than one optional fails when it encounters the first optional in the chain that does not contain a wrapped instance.
Failing gracefully is great for working with optionals safely, but it is something we need to bear in mind when using optional chains because at times an optional chain that fails unexpectedly, or only under certain conditions, may introduce hard-to-diagnose bugs in the application. In the remainder of this article, we will consider what happens when an optional chain fails for the use cases discussed above, and ways to deal appropriately with such failures.
1. Failure of an optional chain to retrieve or return a value
When an optional chain is used to retrieve the value of a property of the wrapped instance, retrieve a value from a subscript on the wrapped instance, or call a method on the wrapped instance that returns a value, we can easily find out whether an optional chain has failed because instead of the expected value wrapped in an optional we get nil
. We can deal with such situations by checking for nil
and taking action accordingly.
The code below illustrates the failure of an optional chain used to retrieve the value of a property of the wrapped instance, where the optional does not actually contain a wrapped instance. We attempt to create a person with an empty string for a name, which we know will cause the initializer to fail and return nil
. We then use an optional chain to retrieve the value of the name property, test the result for nil
, and print a message accordingly.
let notAPerson = Person(named: "")
let notAName = notAPerson?.name
if notAName == nil {
print("Person not found")
}
// Person not found
As an additional example, we show the failure of an optional chain used to retrieve a value from a subscript on the wrapped instance, where the optional does not contain a wrapped instance. To do this, we create a valid Person
instance, which by default has its pets
property set to nil
. We use an optional chain to attempt to retrieve the first element of the pets
property, test the result for nil
, and print a message.
let aPerson = Person(named: "Jane")
let aPersonsPet = aPerson?.pets?[0]
if aPersonsPet == nil {
print("Pet not found")
}
// Pet not found
2. Failure of an optional chain to call a method that does not return a value
The above mechanism works for cases where the optional chain is being used to retrieve or return a value, so we can test the resulting optional for nil
to check for failure. But how about when we use an optional chain to call a method that does not return a value.
To demonstrate this, we define a variable of type Pet?
, set its value to nil
, and attempt to call the makeSound()
method on the non-existent wrapped instance.
let notAPet: Pet? = nil
notAPet?.makeSound()
This does not cause an error because we have used an optional chain, but we also don’t get anything printed on the console, which tells us that the optional chain has failed. In this demonstration, we can check for failure by checking the console, but how can we know whether the optional chain failed in a real-world application where we may not be able to directly confirm whether the call to the method succeeded or failed.
This leads us to the important point that there is no such thing in Swift as a function with no return value. All Swift functions have a return type, and return a value of that type. The return type is either the type that we explicitly declare, or the implicit Void
return type with the special return value ()
, an empty tuple. Since optional chains that retrieve or return a value always evaluate to an optional, when we use an optional chain to call a function with no explicit return type, we get a value of type Void?
. We can test this against nil
to check whether or not the optional chain succeeded in calling the method.
if notAPet?.makeSound() == nil {
print("Method not called")
}
// Method not called
We may perform the check for nil
inline, as shown above, or store the implicit return value from the function to check for nil
at a later time.
var implicitReturnValue: Void? = notAPet?.makeSound()
if implicitReturnValue == nil {
print("Method not called")
}
// Method not called
Note that rather than relying on type inference, we have explicitly defined the var implicitReturnValue
to be of type Void?
. If we don’t do this, we will get a warning from the compiler that we are looking for a return value from a function that does not explicitly return a value. So we have to assure the compiler that we know that the function we are calling does not have an explicit return value and we are looking for the implicit return value.
3. Failure of an optional chain to set a value
Finally, we look at cases where an optional chain fails to set the value of a property of the wrapped instance, or to set a value from a subscript on the wrapped instance. For this, we first use an optional chain to attempt to set a value for the name
property using the notAPerson
optional we had defined in the previous section, which does not contain a wrapped Person
instance.
notAPerson?.name = "Jill"
Then we attempt to use an optional chain to set a value from a subscript on the pets
property using the constant aPerson
that we had defined in the previous section, where we know the pets
property is set to an optional with no wrapped [Pet]
instance.
aPerson?.pets?[0] = Cat()
In both the above cases, we know for this simple example that the optional chains should have failed, but we seem to have no direct way to confirm it.
We are helped here by a lesser-known aspect of Swift, which is that setting the value of a property, or setting a value from a subscript, implicitly returns the same value as a function with no explicit return value. So, just as in the previous section where we captured the implicit return value of a function which does not have an explicit return value, we can capture the implicit return value from setting the value of a property, and setting a value from a subscript, to check whether the above optional chains succeeded. As in the previous case, the return type will be Void?
, so we can do an inline check for nil
, or use the implicitReturnValue
variable we had defined earlier.
Let’s try the latter approach for the optional chain we used above to attempt to set a property value.
implicitReturnValue = (notAPerson?.name = "Jill")
if implicitReturnValue == nil {
print("Person does not exist, so name could not be set")
}
// Person does not exist, so name could not be set
We can do the same for the optional chain we used above to attempt to set a value from a subscript.
implicitReturnValue = (aPerson?.pets?[0] = Cat())
if implicitReturnValue == nil {
print("Person does not exit, or pet could not be added")
}
// Person does not exit, or pet could not be added
This shows that both the optional chains above failed, which is as expected. It is noteworthy that in the first case there is a single point of failure, i.e., notAPerson
could be nil
. So we can be sure why the optional chain failed. In the second case, however, there are two possible points of failure, i.e., aPerson
could be nil
, or aPerson
could wrap a Person
instance with the pets
property set to nil
.
As noted earlier, the optional chain will fail as soon as it encounters the first optional that does not wrap an instance, or in other words, the first nil
. In the above example, we know the optional chain failed because aPerson
has its pets
property set to nil
. We demonstrate the case below where there is no wrapped Person
instance by using the notAPerson
optional.
implicitReturnValue = (notAPerson?.pets?[0] = Cat())
if implicitReturnValue == nil {
print("Person does not exit, or pet could not be added")
}
// Person does not exit, or pet could not be added
In both cases, we print the same failure message because the failure of the optional chain does not give us enough information to know which of the two tests for nil
failed. In case we need that information for the purposes of our application, we will need to break up the optional chain to isolate the relevant point of failure.
Optionals wrapping optionals
As noted earlier in this article, an optional can wrap an instance of any Swift type. Since Optional
is a Swift type, we can have an optional wrapping another optional. In fact, there can be any number of nested optionals.
Creating a nested optional is simple – just add additional question marks. Each additional question mark creates an additional level of nesting. As an example, we create an optional of type Pet?
containing a wrapped Cat
instance, with two additional levels of nesting.
var nestedPet: Pet??? = Cat()
print(type(of: nestedPet))
// Optional<Optional<Optional<Pet>>>
Unsurprisingly, the type of nestedPet
is Optional<Optional<Optional<Pet>>>
(or Pet???
), which is the type we have specified above.
The good news is that optional chaining takes dealing with nested optionals in its stride, regardless of the levels of nesting. We simply add as many additional question marks as the levels of nesting to access the wrapped instance. We show below how we can use an optional chain to set and retrieve the name
property of the Cat
instance wrapped in the above nested optional.
nestedPet???.name = "Bella"
let nestedPetName = nestedPet???.name
print(nestedPetName ?? "Pet name not found") // Bella
Similarly, we can use an optional chain to call a method on an instance wrapped in a nested optional.
nestedPet???.makeSound() // Meow
To demonstrate setting and retrieving a value from a subscript on an instance wrapped in a nested optional, we create a new type representing a person who has a nested array of pets.
class PersonWithNestedPets {
var name: String
var pets: [Pet]???
init?(named name: String) {
guard !name.isEmpty else { return nil }
self.name = name
}
}
We create a new instance of the above type and use an optional chain in the normal manner to set a value for the array of pets.
var personWithNestedPets = PersonWithNestedPets(named: "Ralph")
personWithNestedPets?.pets = [Cat()]
Now, we can use an optional chain, with additional question marks, to set and retrieve the value of the name
property of the cat owned by the person.
personWithNestedPets?.pets???[0].name = "Fluffy"
let personsNestedPetName = personWithNestedPets?.pets???[0].name
print(personsNestedPetName ?? "Pet name not found") // Fluffy
While, as we have seen above, we can easily deal with nested optionals using an optional chain, we are not likely to commonly encounter nested optionals in Swift. Under “normal” circumstances, a value obtained using an optional chain, whether it is the value of a property of the wrapped instance, a value retrieved from a subscript on the wrapped instance, or a value returned by a method of the wrapped instance, will not be a nested optional regardless of the number of optionals included in the optional chain, or the number of levels of nesting of a particular optional in the chain. Having said this, consider the following snippet of code.
let annotationView = MKAnnotationView()
let annotationTitle = annotationView.annotation?.title
print(type(of: annotationTitle)) // Optional<Optional<String>>
This is an example of a few instances where things can get a bit different on the margins, when we deal with Objective-C classes bridged to Swift. This particular situation where an optional chain yields a nested optional, which comes about when dealing with MapKit
, was kindly pointed out by Mike Chartier. (In fact, this section of the article was added based on Mike’s suggestion.) Since the introduction of Swift, Apple engineers have been hard at work reducing the likelihood of such paradigm-altering interactions for Swift programmers. We are not there yet, as the above example illustrates, but monumental progress has already been made, and more continues to be done every day.
As long as there are cases that we have to deal with that can produce such anomalies, the main thing to do is to be aware of what to expect, so we can take appropriate action. An optional chain, as already noted, can easily deal with a nested optional as long as we know where we are likely to encounter such situations, so they don’t take us by surprise and we can proactively make appropriate modifications to our code.
Conclusion
Optional chaining is an extremely useful and powerful feature of Swift, which allows us to work with optionals without having to unwrap them, and without having to materially change the way in which we normally access properties and subscripts, or call methods. Since optional chains fail gracefully when they do, which is great because our application will not crash and burn if a nil
is encountered by an optional chain, we may need to build appropriate checks to confirm whether a particular optional chain succeeded if that information is required by the application.
Thank you for reading! I always appreciate constructive comments and feedback. Please feel free to leave a comment in the comments section of this post or start a conversation on Twitter.
Mike Chartier says
Very good summary. You may want to mention the so-called “double optionals” that arise when OBJ-C classes are bridged to Swift. For example, see MKAnnotationView from MapKit. Strange things will happen when you declare a variable of type MKAnnotationView and then try to access some of the optional properties such as annotationView.annotation.title
Khawer Khaliq says
Hi Mike, thanks for your extremely helpful comment and suggestion. Much appreciated. As per your suggestion, I have added a section to the article on optionals wrapping optionals, how they can come about and how they can be dealt with using optional chains.