Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
441 lines (345 sloc) 13 KB
layout title key excerpt tags comments lang
article
Demystifying Swift Optionals — Optionals Like a Pro (Part 1)
2019-08-11
In part 1 of the series Optionals Like a Pro, we will take a look at the use case of Swift Optional and how to initialize and unwrap Optionals. In part 2, handling Optionals with optional binding and using implicitly unwrapped optionals will be discussed. In part 3, we will explore a more functional approach to deal with Optionals using map, flatMap, and compactMaps. At the end of the series, you will be able to handle Optionals like a pro. 😎
swift
optional
optionals like a pro
true
en-US

Demystifying Swift Optionals — Optionals Like a Pro (Part 1)

In part 1 of the series Optionals Like a Pro, we will take a look at the use case of Swift Optional and how to initialize and unwrap Optionals. In part 2, handling Optionals with optional binding and using implicitly unwrapped optionals will be discussed. In part 3, we will explore a more functional approach to deal with Optionals using map, flatMap, and compactMaps. At the end of the series, you will be able to handle Optionals like a pro. 😎 Let's get started!

🤷 What is an Optional and when do we use it?

Suppose we are dealing with a remote API containing user information. The API sends us a list of users, containing the user's name, ID, and an email address in a JSON format.

{
  "error": "",
  "users": [
    {
      "name": "Tony Stark",
      "id": "tony3000",
      "email": "tony3000@stark.com"
    },
    {
      "name": "Thor",
      "id": "strongest_avenger"
    },
    {
      "name": "Steve Rogers",
      "id": "captain",
      "email": "captain@shield.com"
    }
  ]
}

Awesome, the users have name, id, and email, so we can define our model like:

struct User: Codable {
  var name: String
  var id: String
  var email: String
}

(If you are wondering what Codable is, take a look at this awesome article from Apple on this topic: Encoding and Decoding Custom Types)

However, note that Thor did not register his email address. In order to create a model to represent the user info fetched by the API, we should be able to handle the case where the user does not have an email information. We might simply make an empty email information with am empty string, "". But is it really empty? Yes it is, but an empty String, which actually is "something". Swift provides us a more semantically correct way to handle an empty state, and is called Optional.

Optionals are used to handle variable that can be, well, optional. Think of it as a container that can either hold a value or be empty. The latter case is called nil. If you are familiar with other languages like C# or Kotlin which also have the same concept with a slightly different name Nullable, you will be familiar to the syntax.

In our case, email property of the User structure can be defined with a type String?:

struct User: Codable {
  var name: String
  var id: String
  var email: String?
}

The trailing ? just means that it is an Optional type, rather than an ordinary String. You can think of it as a "container" around the String type (the term "container" has a fancier name Generic).

So after we properly decode the JSON data to User model type (I'll leave the topic for later), thor will be:

print(thor)  // User(name: "Thor", id: "odinson", email: nil)

Now that we took a glimpse at a practical example of when we use Optionals in Swift, let's see how we handle Optionals!

🎬 Initializing Optionals

A type that represents either a wrapped value or nil, the absence of a value.

  • Apple Documentation

Optional adds a new layer to the actual type a value will have, in order to represent a case where there is no value of the type at all. It has two cases. Does it ring a bell? Yes, it is an enumeration with two cases, none and some, the latter with an associated value. In a nutshell, it is defined like:

public enum Optional<Wrapped>: ExpressibleByNilLiteral {
  case none
  case some(Wrapped)

  public init(_ some: Wrapped) { self = .some(some) }
  public init(nilLiteral: ()) { self = .none }
}

Since it is merely an Enum, we can use it like one:

let optionalFoo: Optional<Int> = .some(3)
let optionalBar: Optional<Int> = .none
print(optionalFoo, optionalBar)  // Optional(3) nil

Using the provided initializer init(_ some: Wrapped) and init(nilLiteral: ()), we can do the same as the following:

let anotherFoo: Optional<Int> = .init(3)  // or simply 3
let anotherBar: Optional<Int> = .init(nilLiteral: ())
print(optionalFoo, optionalBar)  // Optional(1) nil

Note that we will never use the later form of the initializer as it is discouraged by Apple here), and because we can use the handy nil literal:

let nilLiteralBar: Optional<Int> = nil
print(nilLiteralBar)  // nil

If we use the nil literal, the compiler will automatically use the init(nilLiteral: ()) initializer. Furthermore, since Optional is used so much in the Swift world, from type casting to accessing values in a Dictionary, a syntactic sugar 🍭 to easily define an Optional value is provided: the postfix ?. As we briefly saw in the above example, we can define an Optional like:

let sugaredFoo: Int? = 3
print(sugaredFoo)  // Optional(3)

Compare this with Optional<Int>.some(3)! Moreover, if you don't declare an initial value for an Optional, it will default to nil:

var sugaredBar: Int?
print(sugaredBar)  // nil

Note that this is not the case with a constant:

let constantSugaredBar: Int?
// Error: Constant 'constantSugaredBar' used before being initialized

Why? That is because we sometimes want to define a value after declaring it, e.g.,

let declared: Int
declared = 3

but then if an Optional constant defaulted to nil, the following wouldn't be possible:

let declaredOptional: Int?
declaredOptional = 3  // Wouldn't be possible if it defaults to nil

Enough with initializing Optionals, let's get into unwrapping Optionals!

(In case you are wondering about Implicitly Unwrapped Optional, a.k.a. IUO, it is going to be discussed later in part 2, so hold tight!)

📦 Unwrapping Optionals with ??, !, ?

⁇ Nil-Coalescing Operator ??

Like the first example shown in this article, let's say a variable name is of type String?

var name: String? = "Thor"

String? is a generic enumeration type: Optional<String>, as we mentioned earlier. As we normally would do with other Enums, we can do:

switch name {
case .some(let wrapped):
  print(wrapped)
case .none:
  print("nil")
}
// Thor

to get the wrapped value. I admit it; it is a bit cumbersome. But thanks to Swift, we can use a syntactic sugar so that we can easily get a value wrapped inside an Optional:

print(name ?? "nil")  // Thor

using the nil-coalescing operator, ??. The nil-coalescing operator uses the value on the left hand side bar if the value is present, and uses the value provided on the right hand side otherwise. Note that we can provide multiple backups to ?? like:

let first: Int? = nil
let second: Int? = nil
let third: Int? = nil
let fourth: Int? = 4
print(first ?? second ?? third ?? fourth ?? "nil")

Note that if the last value in the chain is an Optional, it won't get unwrapped, so

print(first ?? second ?? third ?? fourth)  // Optional(4)

prints an Optional(4). If it weren't the nil-coalescing operator, this should have been written like:

switch first {
case .some(let wrapped): print(wrapped)
case .none:
  switch second {
  case .some(let wrapped): print(wrapped)
  case .none:
    switch third {
    case .some(let wrapped): print(wrapped)
    case .none:
      switch fourth {
      case .some(let wrapped): print(wrapped)
      case .none:
        print("nil")
      }
    }
  }
}

// Or using a pattern matching binding with associated values
// in Enumerations
if case let .some(value) = first { print(value) }
else if case let .some(value) = second { print(value) }
else if case let .some(value) = third { print(value) }
else if case let .some(value) = fourth { print(value) }
else { print("nil") }

(The second part uses a syntax called pattern matching binding for Enums, which is similar to the optional binding syntax which will be introduced in the next part of the series. 👉)

💥 Forced Unwrapping !

What if we just want to unwrap an Optional no matter what? When we're absolutely sure that the value will be present?? We can use !! (pun intended)

It is another syntactic sugar to unconditionally unwrap optionals with a bang !:

let implicitlyUnwrappedFoo: String? = "foo"
print(implicitlyUnwrappedFoo!)  // foo

This is basically writing:

switch implicitlyUnwrappedFoo {
case .some(let wrapped):
  print(wrapped)
case .none:
  fatalError("Unexpectedly found nil while unwrapping an Optional value")
}

in a more concise way. However, think once more before adding ! to your optional. What seemed to be obviously non-nil might turns out to be nil, crashing your app! 😱

Optional Chaining

Okay, but what should we do if the value wrapped by an Optional is something more complex than an Int or String, like a custom struct or a class? For instance, let's look at the following:

struct Foo { var baz: Int }
var foo: Foo? = Foo(baz: 0)

Of course, we can use a plain old switch again, but let's not do that. Instead, Swift provides us with an optional chaining. In order to fetch the value of baz inside foo if it exists, we can do the following:

print(foo?.baz)  // Optional(0)

Comparing this to force unwrapping with ! like foo!.baz, we can simply replace ! with ? to fetch baz only if foo if non-nil. If it is nil, foo?.baz will be nil. To check the actual type of foo?.baz, we can use type(of:) function.

print(type(of: foo?.baz))  // Optional<Int>

foo?.baz is indeed an Optional, wrapping an Int type: Int?.

Not only can we read a value of baz, we can also write a value to it if foo is non-nil:

foo?.baz = 9
print(foo?.baz)  // Optional(9)

Of course, if foo was a let constant, this would have thrown an error.

Note that we can call a method the same way using optional chaining. Define a new struct Bar and define a new instance bar of it:

struct Bar {
  var qux: Int
  mutating func addToQux(with value: Int) {
    qux += value
  }
}

var bar: Bar? = Bar(qux: 5)
print(bar?.qux)  // Optional(5)

We made a simple function addToQux(with:) that actually mutates the value of qux by the argument it was given. (mutating on the left of func is needed in a struct method if it modifies itself.)

Now, let's apply the method addToQux(with:) to bar using optional chaining:

bar?.addToQux(with: 3)
print(bar?.qux)  // Optional(8)

Let's do more with optional chaining. In Swift, when we access a value of a Dictionary with a key, it returns an Optional type in case the value doesn't exist. To take a look at this behavior, let's create a Dictionary mapping String to Bars:

let barDictionary = [
  "barFoo": Bar(qux: 3),
  "barBar": Bar(qux: 5),
  "barBaz": Bar(qux: 8),
]

If we want to get a value of qux of the one mapped from "barFoo", we can simply chain qux after ?.:

print(barDictionary["barBaz"]?.qux)  // Optional(8)

But some of us would ask: barDictionary was of type [String: Bar?]? Would that result in an Optional wrapping another Optional? 🤔

Indeed:

let optionalBarDictionary: [String: Bar?] = ["barQux": Bar(qux: 7)]
print(optionalBarDictionary["barQux"]??.qux)  // Optional(7)

A nested Optional, that's what you get! 🎁

Finally, let's take a look at nested optional chaining. Lets' take a look at the new structs:

struct Baz { var bar: Bar? }
struct Qux { var baz: Baz? }
struct Quz { var qux: Qux? }

and do

let quz: Quz? = Quz(qux: Qux(baz: Baz(bar: Bar(qux: 8))))

I know, not a very good example, but it will work as a demonstration purpose. If we want to fetch the value 8 nested deep in the model, we can do:

print(quz?.qux?.baz?.bar?.qux)  // Optional(8)

Wait. Why is it not Optional<Optional<...<Optional<Int>>...>>? Because Swift automagically unwraps the Optionals when doing optional chaining for us. It would have been ugly if we should do something like quz??.qux??... every time we nest optional chaining. So remember, if we do optional chaining, an optional property will not become an Optional optional, as the Swift handles it for us.

This is the end of part 1! In part 2, we will take a look at optional binding with if and guard statements, and implicitly unwrapped optionals.

You can’t perform that action at this time.