Swift Optionals

by Dimitris Tasios
21 views

In this article, we will learn how to use optionals in Swift, as well as how to handle optional variables and constants.

1. What Are Swift Optionals?

An optional variable or constant in Swift is like a common variable or constant but with a twist; they can have no value. The “no value” state is represented by a special keyword, nil, which would be Swift’s way of representing the “null” value found in other programming languages. Furthermore, a function can also return an optional value or have optional parameters.

The reason we use optional variables and constants is to better represent that a variable or constant might not always have a value. For example, if we are looking for the index of a number in an array, we could save that index to an optional variable. If it’s nil, then we know that the array doesn’t have the number, otherwise we have the number’s index.

Think of an optional like a wrapped present; we may not know whether the box has a present inside or is empty. In order to be sure, we would have to check that. But before getting into that, let’s see how to declare an optional.

Note: From now, an “optional variable or constant” will be referred simply as “optional“, for brevity.

2. How to Declare an Optional?

A variable or constant can be declared as optional, by declaring its type and following it with a question mark (?). The default value of an optional variable is nil.

// MARK: Declaring an optional
var number: Int? = 5
print(number)

number = nil
print(number)

The output is:

Optional(5)
nil

Our number variable is declared as an optional integer, or Int?. As such, it can either be an integer number or nil, the latter representing a “no value” state.

You might have noticed that printing the variable with a non-nil value does not print the value directly but, rather, it prints Optional(5). Our value is wrapped, just like our wrapped present example, and in order to access it, we have to unwrap it.

3. Unwrapping Optionals in Swift

A wrapped box is no use to anyone; we need its contents. Also in Swift, we need to know the value inside the optional, in order to be able to use it and make decisions accordingly. When it comes to unwrapping optionals in Swift (opening our presents) there are multiple ways to achieve it. These are the following:

  1. Forced unwrapping.
  2. If-else block.
  3. Optional binding.
  4. Optional chaining.
  5. A nil-coalescing operator.

3.1 Forced Unwrapping

Perhaps the most direct, albeit very unsafe, way to unwrap an optional is by forcing unwrapping it. A force unwrap is done with an exclamation mark (!) after the type’s name.

The biggest drawback of forced unwrapping is that it’s not safe, since we are not checking if the optional is not nil first, which can lead to our programs crashing if a nil value suddenly appears there. Take a look at the following example:

var message: String? = "An optional message."
print(message!)

print()

message = nil
print(message!) // our program will crash here

The output is:

An optional message.

__lldb_expr_7/OptionalsPlayground.playground:21: Fatal error: Unexpectedly found nil while unwrapping an Optional value
Playground execution failed:

error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.

* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
  * frame #0: 0x00007fff30740c17 libswiftCore.dylib`closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in Swift._assertionFailure(_: Swift.StaticString, _: Swift.StaticString, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never + 391
    frame #1: 0x00007fff3074097c libswiftCore.dylib`closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in Swift._assertionFailure(_: Swift.StaticString, _: Swift.StaticString, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never + 300
    frame #2: 0x00007fff30740655 libswiftCore.dylib`closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in Swift._assertionFailure(_: Swift.StaticString, _: Swift.StaticString, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never + 117
    frame #3: 0x00007fff307402aa libswiftCore.dylib`Swift._assertionFailure(_: Swift.StaticString, _: Swift.StaticString, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never + 314
    frame #4: 0x0000000107048cf5 $__lldb_expr8`main at OptionalsPlayground.playground:0
    frame #5: 0x0000000106cd8460 OptionalsPlayground`linkResources + 256
    frame #6: 0x00007fff2036cbab CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
    frame #7: 0x00007fff2036bfb1 CoreFoundation`__CFRunLoopDoBlocks + 443
    frame #8: 0x00007fff20366952 CoreFoundation`__CFRunLoopRun + 892
    frame #9: 0x00007fff203660f3 CoreFoundation`CFRunLoopRunSpecific + 567
    frame #10: 0x00007fff2c995cd3 GraphicsServices`GSEventRunModal + 139
    frame #11: 0x00007fff25059f42 UIKitCore`-[UIApplication _run] + 928
    frame #12: 0x00007fff2505eb5e UIKitCore`UIApplicationMain + 101
    frame #13: 0x0000000106cd8518 OptionalsPlayground`main + 184
    frame #14: 0x0000000106efeee9 dyld_sim`start_sim + 10
    frame #15: 0x00000001161e64fe dyld`start + 462

Let’s see what happened here:

  • Line 2: Unlike our previous example, by explicitly unwrapping our variable when printing it, we do not get the “Optional()” wrap but the value itself.
  • Line 6: We assign our message variable with a nil value. In this example, we did it ourselves, but it’s important to note that this will most probably happen accidentally (think of our “index of a number in an array” example we mentioned earlier).
  • Line 7: Due to our previous assignment, our program would crash at this line, because we are trying to print a nil value.

As you can see, we never made any checks about whether message was nil before performing an action on it; we were reckless and paid the price. As such, the forced unwrapping method should be avoided.

3.2 If-Else Block

This method only checks whether an optional is nil or not but, it doesn’t unwrap the optional if it’s not nil. In order to unwrap it, we have to use force unwrapping or any other of the other methods of this section.

func printNumberIfNotNil(number: Int?) {
    if number != nil {
        print(number!)
    } else {
        print("Variable is nil!")
    }
}

var ifElseVariable: Int? = 10
printNumberIfNotNil(number: ifElseVariable)

ifElseVariable = nil
printNumberIfNotNil(number: ifElseVariable)

The output is:

10
Variable is nil!

The printNumberIfNotNil(number: Int?) function accepts an optional integer as a parameter and prints its value if it’s not nil. If it is nil, then the function prints an appropriate message.

Notice that the parameter is unwrapped after we know it’s not nil. Whenever we want to use the value of the parameter, we would have to always check if it’s not nil and then use it. The following methods unwrap the optional themselves and save the non-nil value at a variable or constant that we can declare, thus saving us the effort of always checking the optional.

3.3 Optional Binding

One of the most common ways to unwrap an optional is to use optional binding, whose logic is like so: “check the optional and, if it’s not nil, save its value to a new variable or constant which can be used safely”. We can achieve optional binding in 2 ways:

  1. If let/if var statement.
  2. Guard let/guard var statement

3.3.1 If Let/If Var Statement

The first way to use optional binding is by using an if statement that checks whether the optional has a value and, at the same time, saves the non-nil value of the optional to a new variable (if var) or constant (if let). Let’s see an example to better understand this statement.

func addTwoIntegers(firstOperand: Int?, secondOperand: Int?) -> Int? {
    if let number1 = firstOperand {
        if let number2 = secondOperand {
            return number1 + number2
        } else {
            return nil
        }
    } else {
        return nil
    }
}

The method above takes 2 optional integers and, if they are both not nil, their sum is returned. Otherwise, if either or both of them are nil, then nil is returned instead.

The if let/if var statement allow multiple assignments in the same if statement, separated by commas, leading to clearer code, like so:

func addTwoIntegersV2(firstOperand: Int?, secondOperand: Int?) -> Int? {
    if let number1 = firstOperand, let number2 = secondOperand {
        return number1 + number2
    } else {
        return nil
    }
}

Then, we call the method (either method) as such:

print(addTwoIntegers(firstOperand: 7, secondOperand: 5))     // 7 + 5 = 12
print(addTwoIntegers(firstOperand: 7, secondOperand: nil))   // 7 + nil = nil
print(addTwoIntegers(firstOperand: nil, secondOperand: 5))   // nil + 5 = nil
print(addTwoIntegers(firstOperand: nil, secondOperand: nil)) // nil + nil = nil

The output is:

Optional(12)
nil
nil
nil

Notice that the output in the first call is Optional(12). This means that, whenever either of those two methods is called, its result should, in turn, be checked as an optional itself. For instance, a possible scenario would be to do the following:

if let tenPlusTwo = addTwoIntegers(firstOperand: 10, secondOperand: 2) {
    print(tenPlusTwo)
}

The output is:

12

As we have mentioned, a function can return an optional. In this case, its return value is then treated as a constant optional, unwrapped in an if let statement.

At this point, the following things should be noted:

  • The scope of the unwrapped optionals in an if let/if var statement is only within the if statement. Even within a function, we cannot access the unwrapped values of an optional, outside the if statement.
  • As seen in the previous version of the addTwoIntegers function, any additional if statement idents the code by one tab space, which can render the code hard to read.
  • Lastly, an if let/if var statement does not need to provide an else statement, which can lead to unpredictable behavior in the program’s flow.

The next statement takes care of all those points.

3.3.2 Guard Let/ Guard Var Statement

Just like the previous statement, a guard let/guard var statement assigns the optional to a local variable or constant. If the optional is nil then we must provide an alternative flow that will terminate the current flow in an else statement, by:

  • returning, with the return keyword, for functions,
  • continuing, with the continue keyword, for loops,
  • breaking, with the break keyword, for loops.

In order to better understand its differences from the if statement, let’s compare the two statements by re-writing the addTwoIntegers function.

func addTwoIntegersIf(firstOperand: Int?, secondOperand: Int?) -> Int? {
    var result: Int?
    
    if let number1 = firstOperand {
        if let number2 = secondOperand { // extra ident, code becomes like a "pyramid"
            result = number1 + number2
        } // no alternative is provided; might have unpredictable behavior (what's the value of "result"?)
    } // number1 and number2 not accessible after this line
    
    return result
}

As you can see, the if statement doesn’t need to provide an alternative path for the flow, if the optional is nil, each if statement moves the code further to the right and, lastly, any optionals unwrapped inside the if statement are not accessible outside of it.

Let’s see the guard statement now:

func addTwoIntegersGuard(firstOperand: Int?, secondOperand: Int?) -> Int? {
    guard let number1 = firstOperand else {
        return nil // must provide an alternative
    }
    
    guard let number2 = secondOperand else { // no extra ident, easily readable
        return nil
    }
    
    return number1 + number2 // both values available outside of guard statement
}

Here, we notice a few things:

  1. If the optional is nil, we must halt the execution of the function, in this case by returning a nil value. We can do as many things as we want in the else block, as long as we finish it with either a return, continue, or break keyword.
  2. All guard statements are at the same level, which is clearer to understand. No extra ident is required.
  3. The unwrapped values of the optionals can be accessed even outside the guard statement, giving us the flexibility to use those values freely everywhere in the function, once they are defined.

Just like in the if statements, we can have multiple unwrapping statements in the same guard statement, separated by commas:

func addTwoIntegersGuardV2(firstOperand: Int?, secondOperand: Int?) -> Int? {
    guard let number1 = firstOperand, let number2 = secondOperand else {
        return nil
    }
    
    return number1 + number2
}

3.4 Optional Chaining

Optional chaining is useful if dealing with multiple optionals in that form some kind of chain. Let’s see an example to better understand this.

In the following example, we have 2 simple structs, one Person who has a name and a number of cars they own, as well as, CarDealership which has an array of people that visit a car dealership on one given day.

struct Person {
    let name: String
    let numberOfCarsOwned: Int?
}

struct CarDealership {
    var todaysCustomers = [Person]()
}

let customerList = [Person(name: "Dimitris", numberOfCarsOwned: 1),
                    Person(name: "Akis", numberOfCarsOwned: nil),
                    Person(name: "Ignis", numberOfCarsOwned: 2)]

let myCarDealership = CarDealership(todaysCustomers: customerList)

let carsOfDimitris = myCarDealership.todaysCustomers.first(where: { $0.name == "Dimitris"})?.numberOfCarsOwned // Optional(1)
print(carsOfDimitris)

let carsOfJohn = myCarDealership.todaysCustomers.first(where: { $0.name == "John"})?.numberOfCarsOwned // nil
print(carsOfJohn)

The output is:

Optional(1)
nil

In the code above, in line 16, we are searching for the first customer whose name is “Dimitris” and then, if that customer exists, get the number of their cars. It’s very possible that a customer named “Dimitris” didn’t visit our car dealership on that specific day, so the result of first(where) function is an optional Person. As such, in order to access the numberOfCarsOwned of the optional Person object, we must place a question mark (?) after the name, to note that this Person might not exist, something that’s the case for the customer whose name is “John”, in line 19.

The question mark, in this case, is how we achieve optional chaining because there is a chain of variables and constants: we start from an optional Person and continue to move further inside that person to see their cars. If somewhere along the chain an optional is nil, then everything else after that optional will be nil.

3.5 Nil-Coalescing Operator

Lastly, the nil-coalescing operator provides the best of both worlds; the shorthand syntax of force unwrapping with the safety of optional binding and optional chaining. The nil-coalescing operator checks an optional and, if it has a value, it assigns it to a variable or constant. Otherwise, it assigns that variable or constant with a default value that we declare. In order to use it, at the right side of an assignment, we need an optional and a non optional of the same type, with double question marks (??) in-between.

var aNumber: Int? = 4
let safeNumber = aNumber ?? 55          // prints 4

aNumber = nil
let anotherSafeNumber = aNumber ?? 43   // prints 43

print(safeNumber)
print(anotherSafeNumber)

The output is:

4
43

As you can see, whether aNumber has a value or not does not affect safeNumber and anotherSafeNumber, as they will always have some value. In the case of safeNumber, the nil-coalescing operator in line 2 returns 4, because aNumber has a non-nil value equal to 4. On the other hand, when aNumber becomes nil, by using a similar nil-coalescing operator as before in line 5, the operator opts for the default value of 43 for the assignment of anotherSafeNumber. In both cases, both constants will have an integer value assigned to them, thanks to the nil-coalescing operator.

4. Conclusion

By now, you should be able to safely use optionals in Swift. You can find the source code (Playground) on our GitHub page.

Related Posts