The reason for implicitly unwrapped optionals

In a previous post, I wrote about implicitly unwrapped optionals. However, I was not happy with the example I created; it felt contrived. The following is the example I used:

let myMailBox = MailBox()
myMailBox.mail = Mail(to: "Alice", from: "Bob")

let myMailImplicitlyUnwrapped: Mail! = myMailBox.mail

print(myMailImplicitlyUnwrapped.to) // the ! is not needed
print(myMailImplicitlyUnwrapped.from)

This code did not seem realistic. It left me with several questions. When would I create code like this? In what scenario would I create the myMailImplicitlyUnwrapped constant? Why wouldn’t I assign Mail(to: "Alice", from: "Bob") to the constant instead. Example:

let myMail: Mail =  Mail(to: "Alice", from: "Bob") 
print(myMail.to) 
print(myMail.from)

I wanted to understand more about implicitly unwrapped optionals. Where are they used and what is their purpose?

After some research I found two use case scenarios:

  • declaring an @IBOutlet
  • avoiding reference cycles

In both cases, their purpose is to create simpler code.

@IBOutlet

Declaring an @IBOutlet appears to be the main use case. From Start Developing iOS Apps (Swift):

When a view controller is loaded from a storyboard, the system instantiates the view hierarchy and assigns the appropriate values to all the view controller’s outlets. By the time the view controller’s viewDidLoad() method is called, the system has assigned valid values to all of the controller’s outlets, and you can safely access their contents.

Since the outlet will be set after initialization we want to use it as a non-optional property. If the outlet were an optional, then any code using it would first need to unwrap it. Declaring the outlet as an implicitly unwrapped optional means any calling code can access it directly. This leads to simpler code.

Avoiding reference cycles

A reference cycle is when two classes contain references to each other. A memory leak can occur if the classes contain strong references to each other. The leak occurs because ARC is unable to remove the classes from memory, even when they are no longer in use. This is because each class has a property that references the other class.

There are several ways to create reference cycles and each has their own solution. One scenario to consider is where neither property can be nil after initialization. The Swift documentation describes the situation:

However, there is a third scenario, in which both properties should always have a value, and neither property should ever be nil once initialization is complete. In this scenario, it’s useful to combine an unowned property on one class with an implicitly unwrapped optional property on the other class.

This enables both properties to be accessed directly (without optional unwrapping) once initialization is complete, while still avoiding a reference cycle.

Example from Apple’s documentation:

class Country {
    let name: String
    var capitalCity: City!
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}
 
class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

Declaring capitalCity as an implicitly unwrapped optional means it is initially nil. The nil value allows the Country class to initialize and pass self to the City initializer. After Country and City are initialized, capitalCity will have a non-nil value. Now, any code using capitalCity can access it directly. If capitalCity were an optional, it would need to be unwrapped when used. But as an implicitly unwrapped optional, this is not needed. So, again the implicitly unwrapped optional makes the code simpler to work with.

Thank you for reading! If I missed a case for implicitly unwrapped optionals, please let me know!

References