• Skip to main content
  • Skip to footer

Khawer Khaliq

  • Home

The Power of Optional Chaining in Swift

Share
Tweet
Share
Pin

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:

  • What Are Swift Optionals and How They Are Used
  • Unwrapping Optionals With Optional Binding in Swift

Contents

What is optional chaining
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.

Subscribe to get notifications of new posts

No spam. Unsubscribe any time.

Reader Interactions

Comments

  1. Mike Chartier says

    July 30, 2021 at 6:44 pm

    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

    Reply
    • Khawer Khaliq says

      August 1, 2021 at 8:07 pm

      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.

      Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Footer

Protocol-Oriented Programming (POP) in Swift

Use protocol-oriented programming to think about abstractions in a completely different way, leveraging retroactive modeling to introduce appropriate abstractions at any point in the development cycle, and creating traits that can let types opt into functionality simply by conforming to a protocol.

Pattern Matching With Optionals in Swift

The optional pattern explained in detail, including how to use it with a variety of conditional statements and loops, and how to add extra conditions when required, with a section on creating more complex pattern matching code involving optionals.

Test-Driven Development (TDD) in Swift

Learn how to use Test-Driven Development (TDD) in Swift which not only enables you to write more reliable and maintainable code but also allows refactoring of code in small increments and with greater ease and confidence.

Unwrapping Optionals With Optional Binding in Swift

Learn how to use optional binding to extract the value wrapped by an optional to a constant or variable, as part of a conditional statement or loop, exploring where optional chaining may be used in place of optional binding, and where these techniques can be used together.

Unit Testing and UI Testing in Swift

Learn how to use unit testing to gain confidence in the correctness of code at the unit level, and use UI testing to ensure that the application fulfills user requirements, explained in detail and illustrated using an example application built using SwiftUI.

When and How to Use the Equatable and Identifiable Protocols in Swift

Detailed coverage of the Equatable and Identifiable protocols, and how they can be used to model not only values but also domain entities with identity using Swift value types, to create code that is more efficient, easier to reason about, easily testable and more concurrency-friendly.

What Are Swift Optionals and How They Are Used

This article explains what Swift optionals are, why Swift has them, how they are implemented, and how Swift optionals can be used to better model real-world domains and write safer and more expressive code.

A Protocol-Oriented Approach to Associated Types and Self Requirements in Swift

Use protocol-oriented programming to avoid having to use associated types in many situations but also to effectively use associated types and Self requirements, where appropriate, to leverage their benefits while avoiding the pitfalls.

Encapsulating Domain Data, Logic and Business Rules With Value Types in Swift

Leverage the power of Swift value types to manage domain complexity by creating rich domain-specific value types to encapsulate domain data, logic and business rules, keeping classes lean and focused on maintaining the identity of entities and managing state changes through their life cycles.

Rethinking Design Patterns in Swift – State Pattern

The State pattern, made simpler and more flexible with the power of Swift, with a detailed worked example to illustrate handling of new requirements, also looking at key design and implementation considerations and the benefits and practical applications of the pattern.

Conditional Logic With and Without Conditional Statements in Swift

Shows how to implement conditional logic using conditional statements as well as data structures, types and flow control mechanisms such as loops, balancing simplicity and clarity with flexibility and future-proofing.

Better Generic Types in Swift With the Numeric Protocol

Covers use of the Numeric protocol as a constraint on type parameters of generic types to ensure that certain type parameters can only be used with numeric types, using protocol composition to add relevant functionality as required.

Understanding, Preventing and Handling Errors in Swift

Examines the likely sources of errors in an application, some ways to prevent errors from occurring and how to implement error handling, using the error handling model built into Swift, covering the powerful tools, associated techniques and how to apply them in practice to build robust and resilient applications.

When and How to Use Value and Reference Types in Swift

Explores the semantic differences between value and reference types, some of the defining characteristics of values and key benefits of using value types in Swift, leading into a discussion on how value and reference types play a complementary role in modeling real-world domains and designing applications.

Swift Protocols Don’t Play Nice With Equatable. Or Can They? (Part Two)

Uses type erasure to implement Equatable conformance at the protocol level, allowing us to program to abstractions using protocol types while safely making equality comparisons and using functionality provided by the Swift Standard Library only available to types that conform to Equatable.

Copyright © Khawer Khaliq, 2017-25. All rights reserved.