Mastering Swift 3
上QQ阅读APP看书,第一时间看更新

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 the for-in loop executes and hold the current item from the collection or range
  • Collection/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.