One of the challenges, when picking up Swift, is to understand how optional work. New learners, usually get confused whether they should use ? or ! when dealing with optional.

In this article, let’s dive deep into the concept behind optional, downcasting and initialization, by exploring the usage of both ? and ! operators.

If you are a beginner in Swift, this article should give you a good overview on how and when to use ? and ! .

For experienced Swift developer, you can use this as a quick reference whenever you have doubt while dealing with both ? and ! operators.

With all that being said, let’s get started! 🚀

Declaration

var myVariable: MyType

Declare myVariable as non-optional type — MyType .

as non-optional type — . myVariable will not be nil for its entire lifetime.

var myVariable: MyType?

Declare myVariable as optional MyType .

as optional . myVariable can be nil at some point of its lifetime.

can be at some point of its lifetime. Value of myVariable can only be accessed by using optional binding or forced unwrapping. (more on optional binding and forced unwrapping later)

var myVariable: MyType!

Declare myVariable as implicitly unwrapped optional MyType .

as implicitly unwrapped optional . myVariable can be nil at some point of its lifetime.

can be at some point of its lifetime. Value of myVariable can be accessed with or without using optional binding.

can be accessed using optional binding. If myVariable is nil and it is being access without using optional binding, runtime error will trigger.

Important Notes

Always declare variables as non-optional type.

Set variables as optional type only when you are not able to initialize the variables during its class or struct initialization, or the variables might become nil at some point of its lifetime after being initialized.

at some point of its lifetime after being initialized. Avoid using implicitly unwrapped optional type as it might cause a runtime error. Set variables as implicitly unwrapped optional type only when you are not able to initialize the variables during its class or struct initialization, but you are confident that the variables will not be nil for its entire lifetime after being initialized. The best example of implicitly unwrapped optional type are the IBOutlets you created for your view controller.

Unwrapping

In this section, we will look into the basic concept and ways we can use to access value of optional type, mainly optional binding and forced unwrapping.

Optional Binding

Code will fail gracefully when try to access optional variable with nil value, no runtime error will be triggered.

value, no runtime error will be triggered. Code snippet below demonstrate how optional binding works.

let str: String? = "test" // Optional binding using 'if' if let value = str { print(value) } // Optional binding using guard guard let value = str else { return } print(value) // Optional chaining if let value = str?.data(using: .utf8)?.first { print(value) }

Note that optional binding can also be used on implicitly unwrapped optional type. However this is quite uncommon and rather redundant.

Forced Unwrapping

Use the ! operator to forcefully access value of optional type.

operator to forcefully access value of optional type. Will trigger a runtime error when the optional variable is nil .

when the optional variable is . Following is how to use the ! operator to force unwrap an optional variable.

let myString: String? = “test” let value = myString! // Forced unwrapping

Important Notes

Optional binding is a much more elegant way to unwrap optional variable compared to forced unwrapping, always use optional binding to unwrap optional value.

Forced unwrapping is dangerous to use, avoid using it if possible.

Downcasting

Both ? and ! operator are very important when dealing with downcasting in Swift. However, developer sometimes might get confused whether he or she should use as , as! or as? when performing downcasting.

as

as keyword is used to converting one type to another when compiler is guaranteeing the success of the cast.

keyword is used to converting one type to another when compiler is guaranteeing the success of the cast. The common use cases will be casting from String to NSString , NSDate to Date or casting an object back to its parent class type.

as?

Use as? keyword when the dwoncasting operation might fail. We call this conditional cast.

keyword when the dwoncasting operation might fail. We call this conditional cast. Conditional cast will produce an optional form of the desired type.

Return nil when the cast operation failed.

when the cast operation failed. Since the cast result is an optional type, we can use optional binding to access the value of the cast result.

as!

as! is the forced cast keyword.

is the forced cast keyword. Forced cast will produce a non-optional form of the desire type.

When cast operation failed, runtime error will trigger.

will trigger. Since the cast result is a non-optional type, we can access the cast result value without using optional binding.

If you find that it is a bit hard to understand the explanation above. Let’s have a look at the following code snippet that demonstrate how to perform downcasting using each as keyword and what are the outcome for each case, it should clear things up for you.

class Bird { var name: String init(name: String) { self.name = name } } class Chicken: Bird { let canFly = false } class Eagle: Bird { let canFly = true func fly() { print("Eagle is flying.") } } let chicken = Chicken(name: "Chicky") // Casting from subclass to parent class by using 'as' let bird1 = chicken as Bird // Cast successful: bird1 type is Bird let bird2 = chicken as? Bird // Warning: Forced cast from 'Chicken' to 'Bird' always succeeds let bird3 = chicken as! Bird // Warning: Forced cast from 'Chicken' to 'Bird' always succeeds // Add chicken into array of type [Bird] let birds: [Bird] = [chicken] let chicken1 = birds[0] as Chicken // Compile error: 'Bird' is not convertible to 'Chicken' let chicken2 = birds[0] as? Chicken // Cast successful: chicken2 type is Chicken? let chicken3 = birds[0] as! Chicken // Cast successful: chicken3 type is Chicken let eagle1 = birds[0] as Eagle // Compile error: 'Bird' is not convertible to 'Eagle' let eagle2 = birds[0] as? Eagle // Cast successful: eagle2 is nil let eagle3 = birds[0] as! Eagle // Runtime error: Could not cast value of type 'Chicken' to 'Eagle' // Using optional binding to access conditional cast result if let myChicken = birds[0] as? Chicken { print(myChicken.name) }

Downcasting To Optional Types

Based on what has been discussed above, we can explore more by trying to downcast to optional types.

// Create another bird array that accept nil value let otherBirds: [Bird?] = [chicken, nil] // Downcasting chicken to optional type let chicken4 = otherBirds[0] as? Chicken? // Cast successful: chicken4 type is Chicken?? let chicken5 = otherBirds[0] as! Chicken? // Cast successful: chicken5 type is Chicken? let chicken6 = otherBirds[0] as? Chicken! // Compile error: Using '!' is not allowed here let chicken7 = otherBirds[0] as! Chicken! // Compile error: Using '!' is not allowed here // Downcasting nil let chicken8 = otherBirds[1] as? Chicken // Cast successful: chicken6 is nil let chicken9 = otherBirds[1] as! Chicken // Runtime error: Unexpectedly found nil while unwrapping an Optional value

Do not get confused by the ? and ! sign at the end of each casting, if you look closely, they all are having the same behaviour as previous example even thought the desired casting type is an optional type.

Do note that downcasting to implicitly unwrapped optional type is not allowed since Swift 5.0. This really makes sense because downcasting to implicitly unwrapped optional type is theoretically the same as downcasting to optional type, they both yield the same outcome.

Important Notes

Always use optional binding to check for conditional cast result.

Avoid using forced binding that might trigger a runtime error.

Initialization

Next up we will look into failable initializer. When there are possibilities that an object initialization might fail, we will need a failable initializer.

A failable initializer will return nil if it failed to initialize. We mark an initializer as failable by using the ? operator.

class Boy { // Failable initialization init?(age: Int) { // Age cannot be less than 0 if age < 0 { return nil } } } let boy1 = Boy(age: 10) // Boy object initialized let boy2 = Boy(age: -1) // Boy object fail to initialize, boy2 is nil

Further Reading

Here are some other Swift programming language related articles recommended to you :

I hope this article helps you get a clearer picture on how and when to use both ? and ! operators. If you have any comments or questions, just drop it in the comment section below.

Feel free to share this article on your favourite social media using the social media buttons below.

Follow me on Twitter if you want to read more articles like this in future.

Thanks for reading. 🧑🏻‍💻