Day 12 - 100 Days of Swift

8 minute read

Optionals

Day 12 is about optionals. You look at what optionals are, and how to unwrap them with if let and with guard let. You look at forced unwrapping and implicitly unwrapped optionals. You look at nil coalescing and optional chaining. You look at using try? and try! to turn a throwing function into a function that returns an optional. You look at failable initializers which return an optional and you look at casting the type of an object to another type with as?.

Optionals are a way to describe that an object will either be a valid type, or “nothing”. In Swift “nothing” is described as nil:

1
2
3
4
5
6
7
8
// This lets us assign a string if the person has
// a middle name, but we can also signify that
// they don't have one at all with nil
var middleName: String? = nil

// At some point in human history this number will
// exist (probably), but it doesn't yet.
var firstYearHumanLivedOnMars: Int? = nil

When you have an object that might be nil, you can’t safely use it in your functions, so you have to check that it isn’t empty before you use it. This is called “unwrapping” an optional. You can do this with an if let statement:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func describe(_ middleName: String?) {
    if let middleName = middleName {
        print("What? Your middle name is \(middleName)?!")
    } else {
        print("You don't have a middle name?")
    }
}

describe(middleName)
// "You don't have a middle name?"

middleName = "Danger"
describe(middleName)
// "What? Your middle name is Danger?!"

You can also unwrap an optional with a guard let statement. This lets you check the value once at the beginning of your function and return early if you don’t have all the information you need:

1
2
3
4
5
6
7
8
9
10
11
func reciteHistoryOfMars(from year: Int?) {
    guard let firstYear = year else {
        print("We don't even live on mars yet.")
        print("How could there be any history?")
        return
    }
    print("It all started in the year \(firstYear)...")
    // Recite the long history of Mars...
}

reciteHistoryOfMars(from: firstYearHumanLivedOnMars)

If you know that it will not be empty, you can force unwrap an optional with a !. If it is empty and you force unwrap it, your code will crash though:

1
2
3
4
5
6
7
8
9
10
func getAnOptionalThatAlwaysHasAValue() -> String? {
    return "Optional"
}

let optional = getAnOptionalThatAlwaysHasAValue()
print(optional!)

// Obviously this isn't a very practical example, but
// it is always safe to unwrap this particular optional.

Related to force unwrapping is the implicitly unwrapped optional. These are optional under the hood, but are essentially force unwrapped every time they are accessed. They tend to be used for things that are nil when they are created, but get a value before you ever need to access them, and keep that value until you can no longer access them:

1
2
3
4
5
6
7
8
9
// @IBOutlets are implicitly unwrapped by default.
// This is because they don't exist when the thing
// referencing them is initialized,
// but they do before you ever access them.

@IBOutlet weak var titleLabel: UILabel!
// This is the code that automatically gets generated
// when you control+drag from a label
// into its parent view controller.

Another way to unwrap an optional is to give it a default value. You do this with the nil coalescing operator ??. This says “try to unwrap the optional and if you find a value, use it. Otherwise, use the second operand’s value.” These can also be chained:

1
2
3
4
5
6
7
print("Dwight \(middleName ?? "") Schrute")
// "Dwight Danger Schrute"

middleName = nil
var middleName2 = "Kurt"
print("Dwight \(middleName ?? middleName2 ?? "") Schrute")
// "Dwight Kurt Schrute"

If you want to access a property or call a method on an optional value, you can use optional chaining to say “try to unwrap this object, and if you find a value, call this method on it, otherwise skip it.”

1
2
3
4
print(middleName?.count ?? 0) // 0

middleName = "Gary"
print(middleName?.count ?? 0) // 4

If you don’t care about the error a throwing function might throw, you can just have it return an optional with try?:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Previously we tried a throwing function like this
do {
    try revivePatient()
} catch {
    print("Couldn't save them.")
}

// Since we aren't using the error,
// we could do the same thing like this
if let revivedPatient = try? revivePatient() {
    // Celebrate
} else {
    print("Couldn't save them.")
}

If you know it will never through an error, you can force unwrap the optional it returns with try!:

1
2
3
// If you were a really confident doctor,
// you could do this
try! revivePatient()

Sometimes you can’t guarantee that an initializer will work, because of bad input or some other factor. If that is the case, you can mark it as a failable initializer with init?. These initializers will return an optional version of the object:

1
2
3
4
5
6
7
8
9
10
11
12
class Employee {
    var name: String

    init?(name: String) {
        // We don't hire people with short names
        guard name.count > 3 else { return nil }
        self.name = name
    }
}

let jim = Employee(name: "Jim")
print(jim) // nil

If the compiler knows that an object is one type, but you know that the object is actually a subclass of that type, you can cast it as that type with as, as? or as!:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Manager: Employee {
    var subordinates: [Employee] = []
}

let michael = Manager(name: "Michael")!
let dwight = Employee(name: "Dwight")!
let kevin = Employee(name: "Kevin")!

let employees = [michael, dwight, kevin]

// 'employees' will be inferred to be an array of
// Employees, because all the objects in it
// match that type.
for employee in employees {
    print("My name is \(employee.name)", terminator: "")
    // But we know that one of them is a manager,
    // so we can check for that.
    if let manager = employee as? Manager {
        print(" and I am in charge of everyone!")
    } else {
        print(".")
    }
}

// "My name is Michael and I am in charge of everyone!"
// "My name is Dwight."
// "My name is Kevin."

Reflections

Optionals are definitely a pretty “Swifty” thing. I don’t know that I’ve really seen anything exactly like them in any other language. They take a while to get used to, but once you get the hang of them they do make things more explicit. Just one more tool in the toolbelt that you can use to convey your intention. And I feel like today provided a pretty good summary of the different ways you can deal with them. I think they first really clicked for me when I realized that a String and a String? are fundamentally two different types. They have different methods and properties. And you have to do some work to get from one to the other, just like you have to do some work to get from an Int to a Double.