This is a translation from medium.com , please see the original article here: https://medium.com/better-programming/optionals-in-swift-explained-5-things-you-should-know-d737e2d52a9e
Optionals are in Swift’s core and have existed since the first version of Swift. A value Optional lets us write clean code while it may still be attentive to the possible value nil
. If you are new to Swift, you may need to become familiar with the syntax of adding question marks behind properties. Once you get used to them, you can really start to benefit from them, as examples and extensions below.
What is the Optional value in Swift?
Before diving into the things you should know about Optional , first, we should do a good job of learning the basics first. The properties, methods, and registers may return an Optional , which means that the returned value may exist in the value or nil
. Multiple queries that can be joined together are called optional chaining . This is an alternative solution for force unwrapping that is explained in more detail later. Below is an example of optional String
and Optional chaining to print out the number of characters.
1 2 3 | let name: String? = "Antoine van der Lee" print(name?.count ?? 0) |
Attention: operator ??
( nil coalescing ) will be explained below.
1. Forced Unwrapping Optionals
Force unwrapping an optional will return a value if it exists or cause an error when the application runs when its value is nil
. But before we dive into force unwrapping , let’s first glance at the possibilities of opening an optional without coercion.
How to open an optional value
There are many ways to open an optional value in Swift. We can use the guard
command:
1 2 3 4 5 6 | let name: String? = "Antoine van der Lee" guard let unwrappedName = name else { return } print(unwrappedName.count) |
Or, we can use the if let
command:
1 2 3 4 5 | let name: String? = "Antoine van der Lee" if let unwrappedName = name { print(unwrappedName.count) } |
Or, we can use the 2 question mark operator. It will return a value if an existing value or a default value exists. For example, the default value is 0.
1 2 3 | let name: String? = "Antoine van der Lee" print(name?.count ?? 0) |
Forced Unwrapping an optional using exclamation points (!)
An optional can be forcefully opened, using the exclamation point ( !
) Immediately after the optional value.
1 2 3 | var name: String? = "Antoine van der Lee" print(name!.count) |
Whenever the name variable in the above example is attached to nil
, it could be the cause of the error when the program is running as follows:
1 2 | Fatal error: Unexpectedly found nil while unwrapping an Optional value |
Opening optional can be done in sequence
Optional chaining can be done as follows:
1 2 3 4 5 6 7 | struct BlogPost { let title: String? } let post: BlogPost? = BlogPost(title: "Learning everything about optionals") print(post?.title?.count ?? 0) |
The result will be the same as we use force-unwrapping:
1 2 3 | let post: BlogPost? = BlogPost(title: "Learning everything about optionals") print(post!.title!.count) |
Please note that if we only open the last optional, we will still only get one result as optional. The example below shows force-unwrapping the title variable, but not the post variable. This means that if the post is nil
, we may still not get the title :
1 2 3 | let post: BlogPost? = BlogPost(title: "Learning everything about optionals") print(post?.title!.count) // Prints: Optional(35) |
Optionals are the best way, force unwrapping to catch programming errors
Use optional as a default, avoid using exclamation marks if not absolutely necessary. Some even recommend enabling force-unwrapping under SwiftLint rules . This will prevent a lot of unexpected incidents. However, there are occasional good cases to use force unwrapping, such as when an error is required when the value is nil
. When we want to debug, use force unwrapping to catch errors early.
2. An optional is an Enum with 2 cases
It’s good to know that Optional is basically an Enum with 2 cases:
1 2 3 4 5 6 7 8 | enum Optional<Wrapped> { /// The absence of a value. case none /// The presence of a value, stored as `Wrapped`. case some(Wrapped) } |
However, instead of using the .none
case, we should use the value nil
to indicate the absence of a value. Remember, we can define the name
variable above as optional by using enum:
1 2 3 | let name = Optional.some("Antoine van der Lee") print(name!.count) |
Or we can switch an optional like with a regular enum :
1 2 3 4 5 6 7 8 9 10 11 12 | func printName(_ name: String?) { switch name { case .some(let unwrappedValue): print("Name is (unwrappedValue)") case .none: print("Name is nil") } } printName(nil) // Prints: "Name is nil" printName("Antoine van der Lee") // Prints: "Name is Antoine van der Lee" |
And looking at its documentation , you can see that an option comes with some handy methods, such as the map
method:
1 2 3 4 | let sideLength: Int? = Int("20") let possibleSquare = sideLength.map { $0 * $0 } print(possibleSquare) // Prints: "Optional(400)" |
Or the flatMap
method, in the example below only returns the name if it has at least 5 characters:
1 2 3 4 5 6 7 | var name: String? = "Antoine van der Lee" let validName = name.flatMap { name -> String? in guard name.count > 5 else { return nil } return name } print(validName) // Prints: "Optional("Antoine van der Lee")" |
Expand the optional
Now that we know that an optional is defined as an enum, we can guess that we can also write an extension for it! The most common example is to expand the optional String
and handle the blank value:
1 2 3 4 5 6 7 8 9 10 11 | extension Optional where Wrapped == String { var orEmpty: String { return self ?? "" } } var name: String? = "Antoine van der Lee" print(name.orEmpty) // Prints: "Antoine van der Lee" name = nil print(name.orEmpty) // Prints: "" |
3. Write Unit Test for optional
When you have Unit Tests, there is a good way to work with options without resorting to force unwrapping. If you use force unwrapping, you risk a fatal error that will prevent all of your unit tests from succeeding. You can use XCTUnwrap
to throw an error if the optional doesn’t contain the value:
1 2 3 4 5 6 | func testBlogPostTitle() throws { let blogPost: BlogPost? = fetchSampleBlogPost() let unwrappedTitle = try XCTUnwrap(blogPost?.title, "Title should be set") XCTAssertEqual(unwrappedTitle, "Learning everything about optionals") } |
4. Methods of Optional Protocol
If you have experience with Objective-C, you may have missed the methods of Optional Protocol. Although there is a better way to write Optional Protocol methods in Swift, the most common way in standard libraries looks like this:
1 2 3 4 5 6 7 | protocol UITableViewDataSource : NSObjectProtocol { optional func numberOfSections(in tableView: UITableView) -> Int // ... } |
This allows you to call the method with a question mark like this:
1 2 3 | let tableView = UITableView() let numberOfSections = tableView.dataSource?.numberOfSections?(in: tableView) |
5. Optional nested is valid
Although this document covers how to eliminate one of the most common causes of nested options, it is still a valid existence!
1 2 3 | var name: String?? = "Antoine van der Lee" print(name!!.count) |
You can open an optional and still return another option. Was this the case when you used the try?
operator try?
in previous versions of Swift. A common example is when you work with dictionaries containing optional values:
1 2 3 4 5 6 | let nameAndAges: [String:Int?] = ["Antoine van der Lee": 28] let antoinesAge = nameAndAges["Antoine van der Lee"] print(antoinesAge) // Prints: "Optional(Optional(28))" print(antoinesAge!) // Prints: "Optional(28)" print(antoinesAge!!) // Prints: "28" |
You may find that it basically only requires you to use an extra exclamation point or question mark.
summary
We’ve covered a lot of things you need to know when working with optional options in Swift. From the basics of opening an optional using an exclamation point ( !!
) to advanced implementation of Optional enum expansion.