Control flow
Control flow, also known as flow of control, refers to the order in which statements, instructions, or functions are executed within an application. Swift supports most of the familiar control flow statements that are in C-like languages. These include loops (including while
), conditional statements (including if
and switch
), and the transfer of the control statements (including break
and continue
). It is worthwhile to note that Swift 3 does not include the traditional C for
loop, and rather than the traditional do-while
loop, Swift has the repeat-while
loop.
In addition to the standard C control flow statements, Swift has also included statements such as the for-in
loop and enhanced some of the existing statements, such as the switch
statement.
Let's begin by looking at conditional statements in Swift.
Conditional statements
A conditional statement will check a condition and execute a block of code only if the condition is true. Swift provides both the if
and if-else
conditional statements. Let's take a look at how to use these conditional statements to execute blocks of code if a specified condition is true.
The if statement
The if
statement will check the conditional statement and if it is true it will execute the block of code. The if
statement takes the following format:
if condition { block of code }
Now, let's look at how to use the if
statement:
let teamOneScore = 7 let teamTwoScore = 6 if teamOneScore > teamTwoScore { print("Team One Won") }
In the preceding example, we begin by setting the teamOneScore
and teamTwoScore
constants. We then use the if
statement to check whether the value of teamOneScore
is greater than the value of teamTwoScore
. If the value is greater, we print Team One Won
to the console. If we run this code, we will indeed see that Team One Won
is printed to the console, but if the value of teamTwoScore
is greater than the value of teamOneScore
, nothing would be printed to the console. That would not be the best way to write an application because we would want the user to know which team actually won. The if-else
statement can help us with this problem.
Conditional code execution with the if-else statement
The if-else
statement will check the conditional statement and if it is true, it will execute a block of code. If the conditional statement is not true, it will execute a separate block of code. The if-else
statement follows this format:
if condition { block of code if true } else { block of code if not true }
Let's modify the preceding example to use the if-else
statement to tell the user which team won:
var teamOneScore = 7 var teamTwoScore = 6 if teamOneScore > teamTwoScore { print("Team One Won") } else { print("Team Two Won") }
This new version will print out Team One Won
if the value of teamOneScore
is greater than the value of teamTwoScore
; otherwise, it will print out the message, Team Two Won
. What do you think the code will do if the value of teamOneScore
is equal to the value of teamTwoScore
? In the real world, we will have a tie, but in the preceding code, we will print out Team Two Won
; this would not be fair to team one. In cases like this, we can use multiple else if
statements and a plain else
statement, as shown in the following example:
var teamOneScore = 7 var teamTwoScore = 6 if teamOneScore > teamTwoScore { print("Team One Won") } else if teamTwoScore > teamOneScore { print("Team Two Won") } else { print("We have a tie") }
In the preceding code, if the value of teamOneScore
is greater than the value of teamTwoScore
, we print Team One Won
to the console. We then have an else if
statement and since the if
statement is preceded by the else
statement the conditional statement is checked only if first if statement returns false. Finally, if both the if
statements were false, then we assume that the values are equal and print We have a tie
to the console.
A conditional statement checks the condition once, and if the condition is met, it executes the block of code. What if we wanted to continuously execute the block of code until a condition is met? For this, we would use one of the looping statements that are in Swift. Let's take a look at looping statements in Swift.
The for loop
The for
loop variants are probably the most widely used looping statements. While Swift does not offer the standard C-based for
loop, it does have the for-in
loop. The standard C-based for
loop was removed from the Swift language starting from Swift 3 because it was rarely used. You can read the full proposal to remove the for
loop on the Swift evolution site at https://github.com/apple/swift-evolution/blob/master/proposals/0007-remove-c-style-for-loops.md. The for-in
statement will execute a block of code for each item in a range, collection, or sequence.
Using the for-in loop
The for-in
loop iterates over a collection of items or a range of numbers and executes a block of code for each item in the collection or range. The format for the for-in
statement looks similar to this:
for variable in collection/range { block of code }
As we can see in the preceding code, the for-in
loop has two sections:
Variable
: This variable will change each time thefor-in
loop executes and hold the current item from the collection or rangeCollection/Range
: This is the collection or range to iterate through
Let's take a look at how to use the for-in
loop to iterate through a range of numbers:
for index in 1...5 { print(index) }
In the preceding example, we iterate over a range of numbers from 1
to 5
and print each of the numbers to the console. This particular for-in
statement uses the closed range operator (...
) to give the for-in
loop a range to go through. Swift also provides a second range operator called the half-open range operator (..<
). The half-open range operator iterates through the range of numbers, but does not include the last number. Let's look at how to use the half-open range operator:
for index in 1..<5 { print(index) }
In the closed range operator example (...
), we will see the numbers 1
though 5
printed to the console. In the half-range operator example, the last number (5
) will be excluded; therefore, we will see the numbers 1
though 4
printed to the console.
Now, let's look at how to iterate over an array with the for-in
loop:
var countries = ["USA","UK", "IN"] for item in countries { print(item) }
In the preceding example, we iterate through the countries
array and print each element of the counties
array to the console. As we can see, iterating through an array with the for-in
loop is safer, cleaner, and a lot easier than using the standard C-based for
loop. Using the for-in
loop prevents us from making common mistakes, such as using the <=
(less than or equal too) operator rather than the <
(less than) operator in our conditional statement.
Let's look at how to iterate over a dictionary with the for-in
loop:
var dic = ["USA": "United States", "UK": "United Kingdom", "IN":"India"] for (abbr, name) in dic { print("\(abbr) -- \(name)") }
In the preceding example, we used the for-in
loop to iterate through each key-value pair of a dictionary. In this example, each item in the dictionary is returned as a (key,value) tuple. We can decompose (key,value) tuple members as named constants within the body of the for-in
loop. One thing to note is that since a dictionary does not guarantee the order that items are stored in, the order that they are iterated over may not be the same as the order they were inserted in.
Now, let's look at another type of loop, the while
loop.
The while loop
The while
loop executes a block of code until a condition is met. Swift provides two forms of while
loops; these are the while
and repeat-while
loops. In Swift 2.0, Apple replaced the do-while
loop with the repeat-while
loop. The repeat-while
loop functions exactly as how the do-while
loop did. Swift uses the do
statement for error handling.
We use while
loops when the number of iterations to perform is not known and is usually dependent on some business logic. A while
loop is used when you want to run a loop zero or more times, while a repeat-while
loop is used when you want to run the loop one or more times.
Using the while loop
The while
loop starts by evaluating a conditional statement and then repeatedly executes a block of code if the conditional statement is true. The format for the while
statement is as follows:
while condition { block of code }
Let's look at how to use a while
loop. In the following example, the while
loop will continue to loop if a randomly-generated number is less than 4
. In this example, we are using the arc4random()
function to generate a random number between 0 and 4
:
var ran = 0 while ran < 4 { ran = Int(arc4random() % 5) }
In the preceding example, we begin by initializing the ran
variable to 0. The while
loop then checks the ran
variable, and if its value is less than 4
, a new random number, between 0 and 4
, is generated. The while
loop will continue to loop while the randomly-generated number is less than 4
. Once the randomly-generated number is equal to or greater than 4
, the while
loop will exit.
In the preceding example, the while
loop checks the conditional statement prior to generating a new random number. What if we did not want to check the conditional statement prior to generating a random number? We could generate a random number when we first initialize the ran
variable, but that would mean we would need to duplicate the code that generates the random numbers, and duplicating code is never an ideal solution. It would be preferable to use the repeat-while
loop for such instances.
Using the repeat-while loop
The difference between the while
and repeat-while
loops is that the while
loops check the conditional statement prior to executing the block of code the first time; therefore, all the variables in the conditional statements need to be initialized prior to executing the while
loop. The repeat-while
loop will run through the loop block prior to checking the conditional statement for the first time; this means that we can initialize the variables in the conditional block of code. Use of the repeat-while
loop is preferred when the conditional statement is dependent on the code in the loop block. The repeat-while
loop takes the following format:
repeat { block of code } while condition
Let's take a look at this specific example by creating a repeat-while
loop where we initialize the variable we are checking, in the conditional while
statement, within the loop block:
var ran: Int repeat { ran = Int(arc4random() % 5) } while ran < 4
In the preceding example, we define the ran
variable as an Int
, but we do not initialize it until we enter the loop block and generate a random number. If we try to do this with the while
loop (leaving the ran
variable uninitialized), we will receive a Variable used before being initialized exception.
The switch statement
The switch
statement takes a value, compares it to the several possible matches, and executes the appropriate block of code based on the first successful match. The switch
statement is an alternative to using the if-else
statement when there could be several possible matches. The switch
statement takes the following format:
switch value { case match1 : block of code case match2 : block of code ...... as many cases as needed default : block of code }
Unlike the switch
statements in most other languages, in Swift, it does not fall through to the next case
statement; therefore, we do not need to use a break
statement to prevent the fall through. This is another safety feature that is built into Swift since one of the most common programming mistakes with the switch
statement made by beginner programmers is to forget the break
statement at the end of the case
statement. Let's look at how to use the switch
statement:
var speed = 300000000 switch speed { case 300000000: print("Speed of light") case 340: print("Speed of sound") default: print("Unknown speed") }
In the preceding example, the switch
statement takes the value of the speed
variable and compares it to the two case
statements, and if the value of speed
matches either case, it will print out what the speed is. If the switch
statement does not find a match, it will print out the Unknown speed
message.
Every switch
statement must have a match for all the possible values. This means that unless we are matching against an enumeration, each switch
statement must have a default
case. Let's look at a case where we do not have a default
case:
var num = 5 switch num { case 1 : print("number is one") case 2 : print("Number is two") case 3 : print("Number is three") }
If we put the preceding code into a Playground and attempt to compile the code, we will receive a switch must be exhaustive, consider adding a default clause
error. This is a compile time error; therefore, we will not be notified until we attempt to compile the code.
It is possible to include multiple items in a single case. To do this, we need to separate the items with a comma. Let's look at how we use the switch
statement to tell us if a character is a vowel or a consonant:
var char : Character = "e" switch char { case "a", "e", "i", "o", "u": print("letter is a vowel") case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z": print("letter is a consonant") default: print("unknown letter") }
We can see in the preceding example that each case has multiple items. Commas separate these items and the switch
statement will attempt to match the char
variable to each item listed in the case
statement.
It is also possible to check the value of a switch
statement to see whether it is included in a range. To do this, we use a range operator in the case
statement, as shown in the following example:
var grade = 93 switch grade { case 90...100: print("Grade is an A") case 80...89: print("Grade is a B") case 70...79: print("Grade is an C") case 60...69: print("Grade is a D") case 0...59: print("Grade is a F") default: print("Unknown Grade") }
In the preceding example, the switch
statement takes the grade
variable and compares it with the grade
ranges in each case
statement, and prints out the appropriate grade.
In Swift, any case
statement can contain an optional guard condition that can provide an additional condition to validate. The guard condition is defined with the where
keyword. Let's say in our preceding example, we had students who were receiving special assistance in class and we wanted to define a grade of D
for them in the range of 55
to 69
. The following example shows how to do this:
var studentId = 4 var grade = 57 switch grade { case 90...100: print("Grade is an A") case 80...89: print("Grade is a B") case 70...79: print("Grade is an C") case 55...69 where studentId == 4: print("Grade is a D for student 4") case 60...69: print("Grade is a D") case 0...59: print("Grade is a F") default: print("Unknown Grade") }
One thing to keep in mind with the guard expression is that Swift will attempt to match the value starting with the first case statement and working its way down checking each case statement in order. This means that if we put the case
statement with the guard expression after the Grade F case
statement, then the case
statement with the guard expression would never be reached. The following example illustrates this:
var studentId = 4 var grade = 57 switch grade { case 90...100: print("Grade is an A") case 80...89: print("Grade is a B") case 70...79: print("Grade is an C") case 60...69: print("Grade is a D") case 0...59: print("Grade is a F") //The following case statement would never be reached because the //grades would always match one of the previous two case 55...69 where studentId == 4: print("Grade is a D for student 4") default: print("Unknown Grade") }
Note
A good rule of thumb is that if you are using guard expressions, always put the case
statements with the guard condition before any similar case
statements without guard expressions.
Switch
statements are also extremely useful for evaluating enumerations. Since an enumeration has a finite number of values, if we provide a case
statement for all the values in the enumeration, we do not need to provide a default case. The following example shows how we can use a switch
statement to evaluate an enumeration:
enum Product { case Book(String, Double, Int) case Puzzle(String, Double) } var order = Product.Book("Mastering Swift 2", 49.99, 2015) switch order { case .Book(let name, let price, let year): print("You ordered the book \(name) for \(price)") case .Puzzle(let name, let price): print("You ordered the Puzzle \(name) for \(price)") }
In this example, we begin by defining an enumeration named Product
with two values each with the associated values. We then create an order
variable of the product type and use the switch
statement to evaluate it. Notice that we did not put a default case at the end of the switch
statement. If we added additional values to the product
enumeration at a later time, we would need to either put a default case at the end of the switch
statement or add more case
statements to handle the additional values.
Using case and where statements with conditional statements
As we saw in the last section, case
and where
statements within a switch
statement can be very powerful. We are able to use these statements with other conditional statements such as if
, for
, and while
statements. Using case
and where
statements within our conditional statements can make our code much smaller and easier to read. Let's look at some examples starting off with using the where
statement to filter the results in a for-in
loop.
Filtering with the where statement
In this section, we will see how we can use the where
statement to filter the results of a for-in
loop. For this example, we will take an array of integers and print out only the even numbers; however, before we look at how we would filter the results with the where
statement, let's look at how we would do this without the where
statement:
for number in 1...30 { if number % 2 == 0 { print(number) } }
In this example, we use a for-in
loop to cycle through the numbers 1
to 30
. Within the for-in
loop, we use an if
conditional statement to filter out the odd numbers. In this simple example, the code is fairly easy to read, but let's see how we can use the where
statement to use less lines of code and make it easier to read:
for number in 1...30 where number % 2 == 0 { print(number) }
We still have the same for-in
loop as the previous example; however, now we put the where
statement at the end, which, in this particular example, we only loop through the even numbers. Using the where
statement shortens our example by two lines and also makes it easier to read because the filter statement is on the same line as the for-in
loop rather than being embedded in the loop itself.
Now let's look at how we could filter with the for-case
statement.
Filtering with the for-case statement
In this next example, we will use the for-case
statement to filter through an array of tuples and print out only the results that match our criteria. The for-case
example is very similar to using the where
statement that we saw earlier where it is designed to eliminate the need for an if
statement within a loop to filter the results. In this example, we will use the for-case
statement to filter through a list of World Series winners and print out the year(s) a particular team won the World Series:
var worldSeriesWinners = [ ("Red Sox", 2004), ("White Sox", 2005), ("Cardinals", 2006), ("Red Sox", 2007), ("Phillies", 2008), ("Yankees", 2009), ("Giants", 2010), ("Cardinals", 2011), ("Giants", 2012), ("Red Sox", 2013), ("Giants", 2014), ("Royals", 2015) ] for case let ("Red Sox", year) in worldSeriesWinners { print(year) }
In this example, we create an array of tuples named worldSeriesWinners
, where each tuple in the array contains the name of the team and the year that they won the World Series. We then use the for-case
statement to filter through the array and only print out the years that the Red Sox won the World Series. The filtering is done within the case
statement where ("Red Sox", year)
says that we want all the results that have the string, "Red Sox"
, in the first item of the tuple and the value of the second item in the year
constant. The for
loop then loops through the results of the case
statement, and we print out the value of the year
constant.
The for-case
statement also makes it very easy to filter out the nil values in an array of optionals. Let's take a look at an example of this:
let myNumbers: [Int?] = [1, 2, nil, 4, 5, nil, 6] for case let .some(num) in myNumbers { print(num) }
In this example, we create an array of optionals named myNumbers
that may contain an integer value or may contain nil. As we will see in Chapter 10, Using Optional Types, an optional is defined as an enumeration internally, as shown in the following code:
enum Optional<Wrapped> { case none, case some(Wrapped) }
If an optional is set to nil, it will have a value of none
, but if it is not nil, then it will have a value of some
with an associate type of the actual value. In our example, when we filter for .some(num)
, we are looking for any optional that has the value of .some (non-nil value)
. As shorthand for .some()
, we could use the ?
(question mark) symbol, as we will see in the following example.
We can also combine for-case
with a where
statement to do additional filtering, as shown in the following example:
let myNumbers: [Int?] = [1, 2, nil, 4, 5, nil, 6] for case let num? in myNumbers where num > 3 { print(num) }
This example is the same as the previous example, except that we put the additional filtering with the where
statement. In the previous example, we looped through all of the non-nil values, but in this example, we loop through the non-nil values that are greater than 3
. Let's see how we do this same filtering without the case
or where
statements:
for num in myNumbers { if let num = num { if num > 3 { print(num) } } }
As we can see, using the for-case
and where
statements can greatly reduce the number of lines needed. It also makes our code much easier to read because all of the filtering statements are on the same line.
Let's look at one more filtering example. This time, we will look at the if-case
statement.
Using the if-case statement
Using the if-case
statement is very similar to using the switch
statement. The majority of the time the switch
statement is preferred when we have over two cases we are trying to match, but there are instances where the if-case
statement is needed. One of these times is when we are only looking for one or two possible matches, and we do not want to handle all of the possible matches. Let's look at an example of this:
enum Identifier { case Name(String) case Number(Int) case NoIdentifier } var playerIdentifier = Identifier.Number(2) if case let .Number(num) = playerIdentifier { print("Player's number is \(num)") }
In this example, we create an enumeration named Identifier
that contains three possible values: Name
, Number
, and NoIdentifier
. We create an instance of the Identifier
enumeration named playerIdentifier
with a value of Number
and an associated value of 2
. We then use the if-case
statement to see if the playerIdentifier
has a value for Number
, and if so, we print a message to the console.
Just like the for-case
statement, we are able to do additional filtering with the where
statement. The following example uses the same Identifier
enumeration as we used in the previous example:
var playerIdentifier = Identifier.Number(2) if case let .Number(num) = playerIdentifier, num == 2 { print("Player is either Xander Bogarts or Derek Jeter") }
In this example, we still use the if-case
statement to see if the playerIdentifier
has a value of Number
, but we've added the where
statement to see if the associated value is equal to 2
, and if so, we identify the player as either Xander Bogarts or Derek Jeter
.
As we saw in our examples, using the case
and where
statements with our conditional statements can reduce the number of lines needed to do certain types of filtering. It can also make our code easier to read. Now let's take a look at control transfer statements.
Control transfer statements
Control transfer statements are used to transfer control to another part of the code. Swift offers six control transfer statements; these are continue
, break
, fallthrough
, guard
, throws
, and return
. We will look at the return
statement in the Functions section later in this chapter and will discuss the throws
statement in Chapter 8, Writing Safer Code with Availability and Error Handling. We will look at the remaining control transfer statements in this section.
The continue statement
The continue
statement tells a loop to stop executing the code block and go to the next iteration of the loop. The following example shows how to use a continue
statement to print out only the odd numbers in a range:
for i in 1...10 { if i % 2 == 0 { continue } print("\(i) is odd") }
In the preceding example, we loop through a range of 1
through 10
. For each iteration of the for-in
loop, we use the remainder (%
) operator to see whether the number is odd or even. If the number is even, the continue
statement tells the loop to immediately go to the next iteration of the loop. If the number is odd, we print out the number is odd and then move ahead. The output of the preceding code is as follows:
1 is odd 3 is odd 5 is odd 7 is odd 9 is odd
Now, let's look at the break
statement.
The break statement
The break
statement immediately ends the execution of a code block within the control flow. The following example shows how to break out of a for
loop when we encounter the first even number:
for i in 1...10 { if i % 2 == 0 { break } print("\(i) is odd") }
In the preceding example, we loop through the range of 1
through 10
. For each iteration of the for
loop, we use the remainder (%
) operator to see whether the number is odd or even. If the number is even, we use the break
statement to immediately exit the loop. If the number is odd, we print out that the number is odd and then go to the next iteration of the loop. The preceding code has the following output:
1 is odd
The fallthrough statement
In Swift, switch
statements do not fall through like other languages; however, we can use the fallthrough
statement to force them to fall through. The fallthrough
statement can be very dangerous because once a match is found, the next case defaults to true and that code block is executed. The following example illustrates this:
var name = "Jon" var sport = "Baseball" switch sport { case "Baseball": print("\(name) plays Baseball") fallthrough case "Basketball": print("\(name) plays Basketball") fallthrough default: print("Unknown sport") }
In the preceding example, since the first case, Baseball
, matches the code and the remaining code blocks also execute, the output looks similar to this:
Jon plays Baseball Jon plays Basketball Unknown sport
The guard statement
In Swift and most modern languages, our conditional statements tend to focus on testing if a condition is true. As an example, the following code tests to see whether the variable x
is greater than 10
, and if so, we perform some function; otherwise, we handle the error condition:
var x = 9 if x > 10 { // Functional code here } else { // Do error condition }
This type of code leads us to having our functional code embedded within our checks and with the error conditions tucked away at the end of our functions, but what if that is not what we really want? Sometimes, it may be nice to take care of our error conditions at the beginning of the function. I know, in our simple example, we could easily check if x
is less than or equal to 10
, and if so, we perform the error condition, but not all the conditional statements are that easy to rewrite, especially the items such as optional binding.
In Swift, we have the guard
statement. The guard
statement focuses on performing a function if a condition is false; this allows us to trap errors and perform the error conditions early in our functions. We could rewrite our previous example using the guard
statement like this:
var x = 9 guard x > 10 else { // Do error condition return } // Functional code here
In this new example, we check to see whether the variable x
is greater than 10
, and if not, we perform our error condition. If the variable x
is greater than 10
, our code continues. You will notice that we have a return
statement embedded within the guard condition. The code within the guard
statement must contain a transfer of control statement; this is what prevents the rest of the code from executing. If we forget the transfer of control
statement, Swift will show a compile time error.
Let's look at some more examples of the guard
statement. The following example shows how we would use the guard
statement to verify that an optional contains a valid value:
func guardFunction(str: String?) { guard let goodStr = str else { print("Input was nil") return } print("Input was \(goodStr)") }
In this example, we create a function named guardFunction()
that accepts an optional that contains a string or nil value. We then use the guard
statement with optional binding to verify that the string optional does not contain a nil. If it does contain nil, then the code within the guard
statement is executed and the return
statement is used to exit the function. The really nice thing about using the guard
statement with optional binding is the new variable is in scope for the rest of the function rather than just within the scope of the optional binding statement.
Now that we have seen how control flow statements work in Swift, let's give an introduction to functions in Swift.