Swift From Scratch: Optionals and Control Flow

In the previous articles, you learned some of the basic concepts of the Swift programming language. If you’ve programmed before, I’m sure you saw a few similarities with other programming languages, such as Ruby, JavaScript, and Objective-C.

Swift From Scratch: Optionals and Control Flow

In this article, we zoom in on control flow in Swift. Before we can discuss control flow in more detail, we need to take a look at a concept that is new to most of you, optionals. Optionals are another safety feature of Swift. At first, it may look like a hassle to use optionals, but you’ll quickly learn that optionals will make your code much safer.

1. Optionals

We’ve already seen that a variable must be initialized before it can be used. Take a look at the following example to better understand what this means.

var str: String

str.isEmpty

If you’re used to working with strings in Objective-C, then you may be surprised that Swift shows you an error. Let’s see what that error tells us.

Swift From Scratch: Optionals and Control Flow

In many languages, variables have an initial default value. In Objective-C, for example, the string in the following code snippet is equal to nil.

NSString *newString;

However, the concept of nil differs in Swift and Objective-C. We’ll discuss nil in more detail a bit later.

What Is an Optional?

Swift uses optionals to encapsulate an important concept, that is, a variable or constant has a value or it hasn’t. It’s that simple in Swift. To declare a variable or constant as optional, we append a question mark to the type of the variable or constant.

var str: String?

The variable str is no longer of type String. It is now of type optional String. This is important to understand. The result or side effect is that we can no longer directly interact with the value of the str variable. The value is safely stored in the optional, and we need to ask the optional for the value it encapsulates.

Forced Unwrapping

One way to access the value of an optional is through forced unwrapping. We can access the value of the variable str by appending an ! to the variable’s name.

var str: String?

str = "Test"

print(str!)

It’s important that you are sure that the optional contains a value when you force unwrap it. If the optional doesn’t have a value and you force unwrap it, Swift will throw an error at you.

Swift From Scratch: Optionals and Control Flow

Optional Binding

There is a safer way to access the value of an optional. We’ll take a closer look at if statements in a few minutes, but the following example shows how we can safely access the value stored in the variable str, which is of type optional String.

var str: String?

if str != nil {
    print(str!)
} else {
    print("str has no value")
}

We first check if the variable str is equal to nil before we print its contents. In this example, str doesn’t have a value, which means it won’t be forced unwrapped by accident.

There’s a more elegant approach called optional binding. In the following example, we assign the value stored in the optional to a temporary constant, which is used in the if statement. The value of the optional str is bound to the constant strConst and used in the if statement. This approach also works for while statements.

var str: String?

str = "Test"

if let strConst = str {
    print(strConst)
} else {
    print("str has no value")
}

What Is nil?

If you’re coming from Objective-C, then you most certainly know what nil is. In Objective-C, nil is a pointer to an object that doesn’t exist. Swift defines nil a bit differently, and it’s important that you understand the difference.

In Swift, nil means the absence of a value, any value. While nil is only applicable to objects in Objective-C, in Swift nil can be used for any type. It’s therefore important to understand that an optional isn’t the equivalent of nil in Objective-C. These concepts are very different.

2. Control Flow

Swift offers a number of common constructs to control the flow of the code you write. If you have any experience programming, then you’ll have no problems getting up to speed with Swift’s control flow constructs, conditional if and switch statements, and for and while loops.

However, Swift wouldn’t be Swift if its control flow didn’t slightly differ from, for example, Objective-C’s control flow constructs. While the details are important, I’m sure they won’t hinder you from getting up to speed with Swift. Let’s start with the most common conditional construct, the if statement.

if

Swift’s if statements are very similar to those found in Objective-C. The main difference is that there’s no need to wrap the condition in parentheses. Curly braces, however, are mandatory. The latter prevents developers from introducing common bugs that are related to writing if statements without curly braces. This is what an if statement looks like in Swift.

let a = 10

if a > 10 {
    print("The value of "a" is greater than 10.")
} else {
    print("The value of "a" is less than or equal to 10.")
}

It should come as no surprise that Swift also defines an else clause. The code in the else clause is executed if the condition is equal to false. It’s also possible to chain if statements as shown in the next example.

let a = 10

if a > 10 {
    print("The value of "a" is greater than 10.")
} else if a > 5 {
    print("The value of "a" is greater than 5.")
} else {
    print("The value of "a" is less than or equal to 5.")
}

There is one important note to make, that is, the condition of an if statement needs to return true or false. This isn’t true for if statements in Objective-C. Take a look at the following if statement in Objective-C.

NSArray *array = @[];

if (array.count) {
    NSLog(@"The array contains one or more items.");
} else {
    NSLog(@"The array is empty.");
}

If we were to port the above code snippet to Swift, we would run into an error. The error isn’t very informative, but Swift does tell us that we need to ensure the result of the condition evaluates to true or false.

Swift From Scratch: Optionals and Control Flow

The correct way to translate the above Objective-C snippet to Swift is by making sure the condition of the if statement evaluates to true or false, as in the following snippet.

let array = [String]()

if array.count > 0 {
    print("The array contains one or more items.")
} else {
    print("The array is empty.")
}

switch

Swift’s switch statement is more powerful than its Objective-C equivalent. It’s also safer, as you’ll learn in a moment. While there are some differences, switch statements in Swift adhere to the same concept as those in other programming languages; a value is passed to the switch statement, and it is compared against possible matching patterns.

That’s right, patterns. As I said, a switch statement in Swift has a few tricks up its sleeve. We’ll take a look at those tricks in a moment. Let’s talk about safety first.

Exhaustive

A switch statement in Swift needs to be exhaustive, meaning that every possible value of the type that’s handed to the switch statement needs to be handled by the switch statement. As in Objective-C, this is easily solved by adding a default case, as shown in the following example.

let a = 10

switch a {
case 0:
    print("a is equal to 0")
case 1:
    print("a is equal to 1")
default:
    print("a has another value")
}

Fallthrough

An important difference with Objective-C’s implementation of switch statements is the lack of implicit fallthrough. The following example doesn’t work in Swift for a few reasons.

let a = 10

switch a {
case 0:
case 1:
    print("a is equal to 1")
default:
    print("a has another value")
}

The first case in which a is compared against 0 doesn’t implicitly fall through to the second case in which a is compared against 1. If you add the above example to your playground, you’ll notice that Swift throws an error at you. The error says that every case needs to include at least one executable statement.

Notice that the cases of the switch statement don’t include break statements to break out of the switch statement. This isn’t required in Swift since implicit fallthrough doesn’t exist in Swift. This will eliminate a range of common bugs caused by unintentional fallthrough.

Patterns

The power of a switch statement in Swift lies in pattern matching. Take a look at the following example in which I’ve used ranges to compare the considered value against.

let a = 10

switch a {
case 0..<5:
    print("The value of a lies between 0 and 4.")
case 5...10:
    print("The value of a lies between 5 and 10.")
default:
    print("The value of a is greater than 10.")
}

The ..< operator or half-open range operator defines a range from the first value to the second value, excluding the second value. The ... operator or closed range operator defines a range from the first value to the second value, including the second value. These operators are very useful in a wide range of situations.

You can also compare the considered value of a switch statement to tuples. Take a look at the following example to see how this works.

let latlng = (34.15, -78.03)

switch latlng {
case (0, 0):
    print("We're at the center of the planet.")
case (0...90, _):
    print("We're in the Northern hemisphere.")
case (-90...0, _):
    print("We're in the Southern hemisphere.")
default:
    print("The coordinate is invalid.")
}

As you can see in the above example, it is possible that the value matches more than one case. When this happens, the first matching case is chosen. The above example also illustrates the use of the underscore. As we saw in the previous article, we can use an underscore, _, to tell Swift which values we’re not interested in.

Value Binding

Value binding is also possible with switch statements, as the following example demonstrates. The second value of the tuple is temporarily bound to the constant description for use in the first and second case.

var response = (200, "OK")

switch response {
case (200..<400, let description):
    print("The request was successful with description (description).")
case (400..<500, let description):
    print("The request was unsuccessful with description (description).")
default:
    print("The request was unsuccessful with no description.")
}

for

The for loop is the first loop construct we’ll take a look at. It behaves very similarly to for loops in other languages. There used to be two flavors, the for loop and the for-in loop. As of Swift 3, however, C-style for loops are no longer available. The following snippet is not possible in Swift 3.

for var i = 0; i < 10; i++ {
    print("i is equal to (i).")
}

If you paste this snippet in a playground, you’ll also notice that the ++ and -- operators are no longer available in Swift 3.

The for-in loop is ideal for looping over the contents of a range or collection. In the following example, we loop over the elements of an array.

let numbers = [1, 2, 3, 5, 8]

for number in numbers {
    print("number is equal to (number)")
}

We can also use for-in loops to loop over the key-value pairs of a dictionary. In the following example, we declare a dictionary and print its contents to the console. As we saw earlier in this series, the sequence of the key-value pairs is undefined since a dictionary is an unordered set of key-value pairs.

var bids = ["Tom": 100, "Bart": 150, "Susan": 120]

for (name, bid) in bids {
    print("(name)'s bid is $(bid).")
}

Each key-value pair of the dictionary is available in the for-in loop as a tuple of named constants. The for-in loop is also great in combination with ranges. I’m sure you agree that the below snippet is easy to read and understand thanks to the use of a closed range.

for i in 1...10 {
    print("i is equal to (i)")
}

while

The while loop comes in two flavors, while and repeat-while. The main difference is that the set of statements of a repeat-while loop is always executed at least once, because the condition of the repeat-while is evaluated at the end of each iteration. The following example illustrates this difference.

var c = 5
var d = 5

while c < d {
    print("c is smaller than d")
}

repeat {
    print("c is smaller than d")
} while c < d

The print statement of the while loop is never executed, while that of the repeat-while loop is executed once.

In many cases, for loops can be rewritten as while loops, and it’s often up to the developer to determine which type of loop to use in a particular situation. The following for and while loops result in the same output.

for i in 0..<10 {
    print(i)
}

var i = 0

while i < 10 {
    print(i)
    i += 1
}

Conclusion

There’s much more to control flow in Swift than what we’ve covered in this article, but you now have a basic understanding to continue your journey into Swift. I hope this tutorial has shown you how Swift’s control flow implementation is very similar to that of other programming languages—but with a twist.

In the rest of this series, we’ll make more use of Swift’s control flow constructs, and you’ll gradually get a better understanding of the subtle differences between Swift and languages like Objective-C. In the next installment of this series, we’ll start exploring functions.

If you want a quick way to get started building apps with the Swift language, we’ve got a course for that!

  • Swift From Scratch: Optionals and Control Flow
    Swift
    Create iOS Apps With Swift 3
    Markus Mühlberger

Or check out some of our other tutorials and courses on Swift and iOS development!

  • Swift From Scratch: Optionals and Control Flow
    iOS
    What’s New in iOS 10
    Markus Mühlberger
  • Swift From Scratch: Optionals and Control Flow
    Swift
    Create an iOS App With Swift 3: Variables, Constants, and Optionals
    Markus Mühlberger
  • Swift From Scratch: Optionals and Control Flow
    iOS SDK
    Create SiriKit Extensions in iOS 10
    Patrick Balestra
  • Swift From Scratch: Optionals and Control Flow
    iOS SDK
    Create an iMessage App in iOS 10
    Davis Allie