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.
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.
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.
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
.
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!
Or check out some of our other tutorials and courses on Swift and iOS development!
Comments