Swift Learning (7) - Control Flow (Improved Code Version)

Swift Learning (7) - Control Flow (Improved Code Version)

August 21, 2021·Jingyao Zhang
Jingyao Zhang

image

Swift provides a variety of control flow structures, including while loops for executing tasks multiple times, if, guard, and switch statements for selecting different code branches based on specific conditions, as well as break and continue statements for jumping to other locations in the code.

Swift also offers the for-in loop, which makes it easier to iterate over arrays, dictionaries, ranges, strings, and other sequence types.

The switch statement in Swift is more powerful than in many C-like languages. case can match many different patterns, including range matching, tuples, and specific type matching. The values matched in a switch statement’s case can be declared as temporary constants or variables for use within the scope of the case, and can also be combined with where to describe more complex matching conditions.


For-In Loops

You can use a for-in loop to iterate over all elements in a collection, such as elements in an array, numbers in a range, or characters in a string.

The following example uses for-in to iterate over all elements in an array:

let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
        print("Hello, \(name)!")
}  // Hello, Anna!  // Hello, Alex!  // Hello, Brian!  // Hello, Jack!
---
output: Hello, Anna!
Hello, Alex!
Hello, Brian!
Hello, Jack!

You can also iterate over a dictionary to access its key-value pairs. When iterating over a dictionary, each element is returned as a (Key, Value) tuple, which can be explicitly named in the for-in loop. In the example below, the dictionary’s key is declared as the constant animalName, and the value as the constant legCount:

let numberOfLegs = ["Spider": 8, "Ant": 6 , "Cat": 4]
for (animalName, legCount) in numberOfLegs {
        print("\(animalName) has \(legCount) legs!")
}  // Cat has 4 legs!  // Ant has 6 legs!  // Spider has 8 legs!
---
output: Ant has 6 legs!
Cat has 4 legs!
Spider has 8 legs!

The contents of a dictionary are theoretically unordered, and the order of elements when iterating is not guaranteed. The order in which elements are inserted into the dictionary does not determine the order in which they are iterated.

for-in loops can also use numeric ranges. The following example outputs part of a multiplication table.

for index in 1...5 {  // By default, index in the loop is a constant and cannot be changed within the loop. If you want a variable, you can use for var index in 1...5 {}
        print("\(index) times 5 is \(index * 5)")
}  // 1 times 5 is 5  // 2 times 5 is 10  // 3 times 5 is 15  // 4 times 5 is 20  // 5 times 5 is 25
---
output: 1 times 5 is 5
2 times 5 is 10
3 times 5 is 15
4 times 5 is 20
5 times 5 is 25

In the example above, the elements to be iterated are represented by the closed range operator ..., indicating the range of numbers from 1 to 5. index is assigned the first number in the closed range (1), and then the statements in the loop are executed once. In this case, the loop contains only one statement, which outputs the result of multiplying the current index by 5. After the statement is executed, the value of index is updated to the second number in the closed range (2), and the print(_:separator:terminator:) function is executed again. This process continues until the end of the closed range.

In the example above, index is a constant that is automatically assigned at the start of each loop iteration. In this case, index does not need to be declared before use; simply include it in the loop declaration for implicit declaration, without needing the let keyword.

If you do not need the value of each item in the range sequence, you can use an underscore _ instead of a variable name to ignore the value:

let base = 3
let power = 10
var answer = 1
for _ in 1...power {
        answer *= base
}
print("\(base) to the power of \(power) is \(answer).")  // Outputs "3 to the power of 10 is 59049."
---
output: 3 to the power of 10 is 59049.

This example calculates the powerth power of base (in this case, 3 to the 10th power), starting from 1 (3 to the 0th power), multiplying by 3 ten times, using a closed range loop from 1 to 10. The calculation does not need to know the value of the counter in each iteration, only that the correct number of iterations is performed. The underscore _ (replacing the variable in the loop) ignores the current value and does not provide access to the value during iteration.

In some cases, you may not want to use a closed range that includes both endpoints. Imagine drawing minute tick marks on a watch face. There are 60 tick marks in total, starting from 0. You can use the half-open range operator ..< to represent a range that includes the lower bound but excludes the upper bound.

let minutes = 60
for tickMark in 0..<minutes {
        // Render a tick mark for each minute
}

Some users may want fewer tick marks in their UI, such as one every 5 minutes, using the stride(from:to:by:) function to skip unnecessary marks.

let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
        // Render a tick mark every 5 minutes (0, 5, 10, 15...50, 55)
}

You can achieve the same effect with a closed range using stride(from:through:by:):

let hours = 12
let hourInterval = 3
for tickMark in stride(from: 3, through: hours, by: hourInterval) {
        // Render a tick mark every 3 hours (3, 6, 9, 12)
}

The above examples use for-in loops to iterate over ranges, arrays, dictionaries, and strings. You can use them to iterate over any collection, including custom classes or collection types that implement the Sequence protocol.

Using for-in to iterate over a Set collection:

let fruitSet: Set<String> = ["Apple", "Orange", "Peach"]
for fruit in fruitSet.sorted() {
        print("I love \(fruit).")
}
---
output: I love Apple.
I love Orange.
I love Peach.

While Loops

A while loop runs a block of statements repeatedly until a condition becomes false. This type of loop is suitable when the number of iterations is unknown before the first iteration. Swift provides two forms of while loops:

  • The while loop, which evaluates the condition at the start of each iteration;
  • The repeat-while loop, which evaluates the condition at the end of each iteration;

while

A while loop starts by evaluating a condition. If the condition is true, it repeats a block of statements until the condition becomes false.

Here is the general format of a while loop:

/*
while condition {
        statements
}
*/

The following example plays a game called Snakes and Ladders:

Snakes And Ladders

Game Rules:

  • The game board has 25 squares, and the goal is to reach or exceed square 25;
  • Each round, you roll a six-sided die to determine how many squares to move, following the path shown by the dashed lines above;
  • If you land at the bottom of a ladder, you can climb up;
  • If you land on the head of a snake, you slide down.

The game board can be represented by an Int array, with its length stored in a constant finalSquare, used to initialize the array and check for victory. The board is initialized with 26 zeros (from 0 to 25, inclusive):

let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
// Some squares are set to specific values to indicate ladders or snakes. The bottom of a ladder is a positive value to move up, the head of a snake is a negative value to move down:
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08

Square 3 is the bottom of a ladder, moving up to square 11, represented by board[03] = +08 (the difference between 11 and 3). For alignment, a unary plus or minus is used, and numbers less than 10 are padded with a leading zero. These syntax tricks are not necessary, just for code neatness.

The player starts at square 0, off the board, and enters the board after the first dice roll:

var square = 0
var diceRoll = 0
while square < finalSquare {
        // Roll the die
        diceRoll += 1
        if diceRoll == 7 { diceRoll = 1 }
        // Move according to the roll
        square += diceRoll
        if square < board.count {
                // Climb the ladder or slide down the snake
                square += board[square]
        }
        print(square, terminator: " ")
}
print("Game Over!")
---
output: 1 11 4 8 13 8 18 20 23 27 Game Over!

This example uses a simple way to simulate rolling a die. diceRoll is not a random number, but starts at 0 and increases by 1 each while loop, resetting to 1 when it reaches 7. So diceRoll cycles through 1, 2, 3, 4, 5, 6, 1, 2, etc.

After rolling, the player moves forward by diceRoll squares. If the player moves past square 25, the game ends. To handle this, the code checks if square is less than board.count before adding board[square] to move up or down.

Note

Without this check (square < board.count), accessing board[square] could go out of bounds and cause a runtime error.

After each while loop iteration, the loop condition is checked again. If the player reaches or exceeds square 25, the loop ends and the game is over. The while loop is suitable here because we don’t know how long the game will last; the loop ends only when a specific condition is met.

Repeat-While

The other form of while loop is repeat-while, which differs in that it executes the code block once before checking the loop condition, then repeats until the condition is false.

Note

The repeat-while loop in Swift is similar to the do-while loop in other languages.

Here is the general format of a repeat-while loop:

/*
 repeat {
         statements
 } while condition
*/

Let’s play Snakes and Ladders again, this time using a repeat-while loop. The values of finalSquare, board, square, and diceRoll are initialized the same as before:

let FinalSquare = 25
var boards = [Int](repeating: 0, count: FinalSquare + 1)
boards[03] = +08; boards[06] = +11; boards[09] = +09; boards[10] = +02
boards[14] = -10; boards[19] = -11; boards[22] = -02; boards[24] = -08
var Square = 0
var DiceRoll = 0

In the repeat-while version, the first step in the loop is to check if the player is on a ladder or snake. No ladder will take the player directly to square 25, so it’s safe to check for ladders or snakes at the start of the loop.

At the start, the player is at square 0, and board[0] is always 0, so it has no effect:

repeat {
        // Climb the ladder or slide down the snake
        Square += boards[Square]
        // Roll the die
        DiceRoll += 1
        if DiceRoll == 7 { DiceRoll = 1 }
        // Move according to the roll
        Square += DiceRoll
        print(Square, terminator: " ")
} while Square < FinalSquare
print("Game Over!")
---
output: 1 3 14 8 13 19 9 20 23 27 Game Over!

After checking for ladders or snakes, the player rolls the die and moves forward by diceRoll squares, ending the current iteration.

The loop condition (while Square < FinalSquare) is the same as before, but is only checked after the loop body. In this game, repeat-while works better than while. The repeat-while version runs Square += boards[Square] immediately if Square hasn’t exceeded the limit, saving the need for an array bounds check.


Conditional Statements

Executing code based on specific conditions is often useful. For example, you may want to run extra code when an error occurs, or display a message when a value is too high or too low. To achieve this, you need conditional statements.

Swift provides two types of conditional statements: if statements and switch statements. Typically, use if statements for simple conditions with few possible cases. Use switch statements for more complex conditions with many combinations, or when pattern matching is needed.

If

The simplest form of an if statement contains only one condition; the code is executed only if the condition is true:

var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 {
        print("It's very cold! Consider wearing a scarf.")
}  // Outputs "It's very cold! Consider wearing a scarf."
---
output: It's very cold! Consider wearing a scarf.

The example checks if the temperature is less than or equal to 32°F (the freezing point of water). If so, it prints a message; otherwise, it does nothing and continues after the if block.

Of course, if statements allow for two-way branching using an else clause, which executes when the condition is false:

temperatureInFahrenheit = 40
if temperatureInFahrenheit <= 32 {
        print("It's very cold! Consider wearing a scarf.")
} else {
        print("It's not that cold. Wear a t-shirt.")
}  // Outputs "It's not that cold. Wear a t-shirt."
---
output: It's not that cold. Wear a t-shirt.

Clearly, one of the two branches will always be executed. Since the temperature is now 40°F, it’s not that cold, so the else branch is triggered.

You can chain multiple if statements together for more branches:

temperatureInFahrenheit = 90
if temperatureInFahrenheit <= 32 {
        print("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
        print("It's really warm. Don't forget to wear sunscreen.")
} else {
        print("It's not that cold. Wear a t-shirt.")
}  // Outputs "It's really warm. Don't forget to wear sunscreen."
---
output: It's really warm. Don't forget to wear sunscreen.

In the example above, an extra if checks if it’s especially hot. The final else is kept for when it’s neither cold nor hot.

In fact, the final else is optional if you don’t need to handle all cases:

temperatureInFahrenheit = 72
if temperatureInFahrenheit <= 32 {
        print("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
        print("It's really warm. Don't forget to wear sunscreen.")
}

Here, since it’s neither cold nor hot, neither the if nor the else if branch is triggered, so nothing is printed.

Switch

A switch statement tries to match a value against several patterns. Based on the first successful match, the corresponding code is executed. When there are many possible cases, a switch statement is usually used instead of if.

The simplest form of a switch statement compares a value against one or more values of the same type:

/*
 switch some value to consider {
 case value 1:
         respond to value 1
 case value 2, value 3:
         respond to value 2 or 3
         default:
         otherwise, do something else
 }
 */

A switch statement consists of multiple case clauses, each starting with the case keyword. To match more specific values, Swift provides several ways to perform more complex pattern matching, which will be discussed later.

Like if statements, each case is a branch of code execution. The switch statement determines which branch to execute, a process called switching based on the given value.

A switch statement must be exhaustive. This means every possible value must be covered by at least one case. If not all values can be covered, a default branch must be used to cover all remaining values, and it must be the last branch.

The following example uses a switch statement to match a lowercase character named someCharacter:

let someCharacter: Character = "z"
switch someCharacter {
case "a":
        print("The 1st letter of the alphabet.")
case "z":
        print("The last letter of the alphabet.")
default:
        print("Some other character.")
}  // Outputs "The last letter of the alphabet."
---
output: The last letter of the alphabet.

In this example, the first case matches the first letter a, the second matches the last letter z. Because a switch must cover all possible characters, not just letters, the default branch matches all other values, ensuring the switch is exhaustive.

No Implicit Fallthrough

Unlike switch statements in C and Objective-C, in Swift, after the code in a matched case branch is executed, the switch statement ends and does not continue to the next case. That is, you do not need to use break explicitly in each case, making switch safer and easier to use, and avoiding errors from missing break statements.

Note

Although break is not required in Swift, you can still use it to exit a case branch early.

Each case must contain at least one statement. The following code is invalid because the first case is empty:

let anotherCharacter: Character = "a"
switch anotherCharacter {
//case "a": // Invalid, this branch has no statements. Uncommenting will cause a compile error: 'case' label in a 'switch' should have at least one executable statement
case "A":
        print("The letter A.")
default:
        print("Not the letter A.")
}
---
output: Not the letter A.

Unlike in C, Swift’s switch does not match both “A” and “a” together. This avoids accidentally falling through from one case to another, making the code safer and more intuitive.

To match both “a” and “A” in a single case, combine the values with a comma:

let AnotherCharacter: Character = "a"
switch AnotherCharacter {
case "A", "a":
        print("The letter A.")
default:
        print("Not the letter A.")  // Outputs "The letter A."
}
---
output: The letter A.

For readability, compound matches can be written on multiple lines.

Note

If you want to explicitly fall through to the next case, use the fallthrough statement.

Range Matching

A case pattern can also be a range of values. The following example shows how to use range matching to output a natural language description for any number:

let approximateCount = 62
let countedThings = "Moons orbiting Saturn"
let naturalCount: String
switch approximateCount {
case 0:
        naturalCount = "no"
case 1..<5:
        naturalCount = "a few"
case 5..<12:
        naturalCount = "several"
case 12..<100:
        naturalCount = "dozens of"
case 100..<1000:
        naturalCount = "hundreds of"
default:
        naturalCount = "many"
}
print("There are \(naturalCount) \(countedThings).")  // Outputs "There are dozens of Moons orbiting Saturn."
---
output: There are dozens of Moons orbiting Saturn.

In this example, approximateCount is evaluated in a switch statement. Each case is compared in turn. Since approximateCount falls in the 12 to 100 range, naturalCount is set to “dozens of”, and the switch exits.

Tuples

You can use tuples to test multiple values in the same switch statement. Tuple elements can be values or ranges. Use an underscore _ to match any value.

The following example shows how to use a tuple of type (Int, Int) to classify a point (x, y) in the diagram below:

let somePoint = (1, 1)
switch somePoint {
case (0, 0):
        print("\(somePoint) is at the origin.")
case (_, 0):
        print("\(somePoint) is at the x-axis.")
case (0, _):
        print("\(somePoint) is at the y-axis.")
case (-2...2, -2...2):
        print("\(somePoint) is inside the box.")
default:
        print("\(somePoint) is outside of the box.")
}  // Outputs "(1, 1) is inside the box."
---
output: (1, 1) is inside the box.

Coordinate Graph Simple

In the example above, the switch checks if a point is at the origin (0, 0), on the red x-axis, on the orange y-axis, inside a 4x4 blue rectangle centered at the origin, or outside the rectangle.

Unlike C, Swift allows multiple case branches to match the same value. In this example, the point (0, 0) could match all four cases, but only the first matching case is executed. For (0, 0), it matches case (0, 0) first, so the rest are ignored.

Value Bindings

case branches allow you to declare matched values as temporary constants or variables for use within the branch. This is called value binding, because the matched value is bound to a temporary constant or variable within the branch.

The following example classifies a point (x, y), represented as a tuple (Int, Int):

let anotherPoint = (2, 1)
switch anotherPoint {
case (let x, 0):
        print("On the x-axis with an x value of \(x).")
case (0, let y):
        print("On the y-axis with a y value of \(y).")
case let (x, y):
        print("Somewhere else at (\(x), \(y)).")
}  // Somewhere else at (2, 1).
---
output: Somewhere else at (2, 1).

Coordinate Graph Medium

In the example above, the switch checks if a point is on the red x-axis, on the orange y-axis, or not on either axis.

All three cases declare placeholder constants x and y to temporarily get one or both values from the tuple anotherPoint. The first case, case(let x, 0), matches a point with y-coordinate 0 and assigns the x-coordinate to x. Similarly, the second case, case(0, let y), matches a point with x-coordinate 0 and assigns the y-coordinate to y.

Once declared, these temporary constants can be used within their respective case branches, as in this example for printing the point’s type.

Note that this switch does not have a default branch, because the last case, case let (x, y), matches all remaining values, making the switch exhaustive.

where

A case pattern can use a where clause to check additional conditions.

The following example classifies a point (x, y) as shown below:

let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
        print("(\(x), \(y)) is on the line x == y.")
case let (x, y) where x == -y:
        print("(\(x), \(y)) is on the line x == -y." )
case let (x, y):
        print("(\(x), \(y)) is just some arbitrary point.")
}  // Outputs "(1, -1) is on the line x == -y."
---
output: (1, -1) is on the line x == -y.

Coordinate Graph Complex

In the example above, the switch checks if a point is on the green diagonal x == y, on the purple diagonal x == -y, or not on either diagonal.

All three cases declare placeholder constants x and y to temporarily get both values from the tuple yetAnotherPoint. These constants are used as part of the where clause, creating a dynamic filter. The case branch is executed only if the where condition is true.

As in the value binding example, since the last case matches all remaining values, the switch is exhaustive and does not need a default branch.

Compound Cases

When multiple conditions can be handled the same way, you can put them in the same case, separated by commas. If any pattern matches, the branch is executed. If the list is long, you can write it on multiple lines.

let SomeCharacter: Character = "e"
switch SomeCharacter {
case "a", "e", "i", "o", "u":
        print("\(SomeCharacter) 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("\(SomeCharacter) is a consonant.")
default:
        print("\(SomeCharacter) is not a vowel or a consonant.")
}  // Prints "e is a vowel."
---
output: e is a vowel.

The first case matches the five lowercase vowels in English. Similarly, the second matches all lowercase consonants. The default branch matches all other characters.

Compound matches can also include value bindings. All patterns in a compound match must include the same value bindings, and each binding must get the same type of value. This ensures that, no matter which pattern matches, the code in the branch can access the bound value with the same type.

let stillAnotherPoint = (9, 0)
switch stillAnotherPoint {
case (let distance, 0), (0, let distance):
        print("On the axis, \(distance) from the origin.")
default:
        print("Not on an axis.")
}  // Outputs "On the axis, 9 from the origin."
---
output: On the axis, 9 from the origin.

The above case has two patterns: (let distance, 0) matches values on the x-axis, (0, let distance) matches values on the y-axis. Both bind distance as an integer, so the code in the branch can access distance regardless of which pattern matched.


Control Transfer Statements

Control transfer statements change the order in which code is executed, allowing you to jump to different parts of your code. Swift has five control transfer statements:

  • continue
  • break
  • fallthrough
  • return
  • throw

Below, continue, break, and fallthrough are introduced. return will be discussed in the functions chapter, and throw in the error handling chapter.

Continue

The continue statement tells a loop to stop the current iteration and start the next one immediately. It’s like saying “I’m done with this iteration,” but without leaving the loop.

The following example removes vowels and space characters from a lowercase string, generating a cryptic short phrase:

let puzzleInput = "Great minds think alike"
var puzzleOutput = ""
for character in puzzleInput {
        switch character {
        case "a", "e", "i", "o", "u", " ":
                continue
        default:
                puzzleOutput.append(character)
        }
}
print(puzzleOutput)  // Outputs "Grtmndsthnklk"
---
output: Grtmndsthnklk

In the code above, whenever a vowel or space is matched, the continue statement ends the current iteration and starts the next. This means that vowels and spaces are skipped, and only non-matching characters are appended.

Break

The break statement immediately ends the execution of the entire control flow. break can be used in switch or loop statements to exit early.

Break in Loops

When used in a loop, break immediately exits the loop and jumps to the first line after the closing brace } of the loop. No further code in the current iteration or future iterations is executed.

Break in Switch Statements

When used in a switch block, break immediately exits the switch block and jumps to the first line after the closing brace }.

This feature can be used to match or ignore one or more branches. Since Swift’s switch must cover all cases and does not allow empty branches, sometimes you need to explicitly match or ignore a branch. To ignore a branch, write a break statement in it; when matched, the break immediately ends the switch block.

Note

If a switch branch contains only comments, it will cause a compile-time error. Comments are not statements and do not allow a switch branch to be ignored. Use break to ignore a branch.

The following example uses switch to determine if a Character represents the number 1-4 in four different languages. For simplicity, multiple values are included in the same branch.

let numberSymbol: Character = "三"  // The number 3 in Simplified Chinese
var possibleIntegerValue: Int?
switch numberSymbol {
case "1", "١", "一", "๑":
        possibleIntegerValue = 1
case "2", "٢", "二", "๒":
        possibleIntegerValue = 2
case "3", "٣", "三", "๓":
        possibleIntegerValue = 3
case "4", "٤", "四", "๔":
        possibleIntegerValue = 4
default:
        break
}
if let integerValue = possibleIntegerValue {
        print("The integer value of \(numberSymbol) is \(integerValue).")  // Outputs "The integer value of 三 is 3."
} else {
        print("An integer value could not be found for \(numberSymbol).")
}
---
output: The integer value of  is 3.

This example checks if numberSymbol is 1-4 in Latin, Arabic, Chinese, or Thai. If matched, the corresponding switch branch sets the Int? variable possibleIntegerValue.

After the switch block, the code uses optional binding to check if possibleIntegerValue was set. Since it’s an optional, it has an implicit initial value of nil, so only if it was set in one of the first four branches will the binding succeed.

In the example, it’s unrealistic to enumerate all possible Character values, so the default branch covers all unmatched cases. Since nothing needs to be done in the default, only a break is written. Once in the default, break completes the branch, and code continues with the if let statement.

Fallthrough

In Swift, switch statements do not fall through from one case to the next. Instead, once the first matching case completes its statements, the entire switch block ends. In contrast, C requires explicit break statements to prevent falling through. Swift’s avoidance of default fallthrough makes switch clearer and more predictable, avoiding accidental execution of multiple cases.

If you do need C-style fallthrough, use the fallthrough keyword in the desired case. The following example uses fallthrough to create a description of a number:

let integerToDescribe = 5
var description = "The number \(integerToDescribe) is "
switch integerToDescribe {
case 2, 3, 5, 7, 11, 13, 17, 19:
        description += "a prime number, and also "
        fallthrough
default:
        description += "an integer."
}
print(description)  // Prints "The number 5 is a prime number, and also an integer."
---
output: The number 5 is a prime number, and also an integer.

This example defines a String variable description and sets an initial value. The function uses switch logic to check the value of integerToDescribe. If it matches one of the listed primes, it appends a phrase indicating it’s a prime, then uses fallthrough to continue to the default branch, which appends more text.

If integerToDescribe is not a listed prime, it matches the default branch.

After the switch, print(_:separator:terminator:) prints the description. In this example, 5 is correctly identified as a prime.

Note

The fallthrough keyword does not check the matching condition of the next case; it simply continues execution into the next case’s code, just like in C.

Labeled Statements

In Swift, you can nest loops and conditional statements to create complex control flow. Both loops and conditionals can use break to exit the entire block early. Therefore, it’s useful to explicitly indicate which loop or conditional a break is intended to exit. Similarly, if there are many nested loops, it’s useful to indicate which loop a continue should affect.

To do this, you can use a statement label to mark a loop or conditional. For a conditional, use break with the label to exit the marked statement. For a loop, use break or continue with the label to exit or continue the marked statement.

Declare a labeled statement by placing a label before the statement’s keyword on the same line, followed by a colon. Here is the syntax for a labeled while loop; the same rules apply to all loops and conditionals.

/*
 label name: while condition {
         statements
 }
 */

The following example is an adaptation of the Snakes and Ladders game from earlier, using a labeled while loop with break and continue. This version adds a new rule:

  • To win, you must land exactly on square 25.
  • If a dice roll would move you past square 25, you must roll again until you land exactly on square 25. The board is the same as before.

finalSQUARE, BOARD, SQUARE, and diceROLL are initialized as before:

let finalSQUARE = 25
var BOARD = [Int](repeating: 0, count: finalSQUARE + 1)
BOARD[03] = +08; BOARD[06] = +11; BOARD[09] = +09; BOARD[10] = +02
BOARD[14] = -10; BOARD[19] = -11; BOARD[22] = -02; BOARD[24] = -08
var SQUARE = 0
var diceROLL = 0

This version uses a while loop and switch to implement the game logic. The while loop has a label gameLoop to indicate it’s the main game loop.

The loop condition is while SQUARE != finalSQUARE, meaning you must land exactly on square 25.

gameLoop: while SQUARE != finalSQUARE {
        diceROLL += 1
        if diceROLL == 7 { diceROLL = 1 }
        switch SQUARE + diceROLL {
        case finalSQUARE:
                break gameLoop
        case let newSquare where newSquare > finalSQUARE:
                continue gameLoop
        default:
                SQUARE += diceROLL
                SQUARE += BOARD[SQUARE]
        }
        print(SQUARE, terminator: " ")
}
print("Game Over!")
---
output: 1 11 4 8 13 8 18 20 23 16 18 21 Game Over!

Each iteration starts by rolling the die. Unlike before, where the player moved immediately, here a switch considers the result of each move to decide if the player can move.

  • If the roll lands the player exactly on the final square, the game ends. break gameLoop jumps to the first line after the while loop, ending the game.
  • If the roll would move the player past the final square, the move is invalid and the player must roll again. continue gameLoop ends the current iteration and starts the next.
  • In all other cases, the move is valid. The player moves forward by diceROLL, then the game logic checks for ladders or snakes. The iteration ends, and the loop condition is checked again.

Note

If the break did not use the gameLoop label, it would only exit the switch, not the while loop. Using the label makes it clear which block is being exited.

Also, using continue gameLoop is not strictly necessary here, since there is only one loop, but it is allowed and makes the logic clearer, especially alongside break gameLoop.


Early Exit

Like if, the execution of guard depends on a Boolean expression. You can use a guard statement to require a condition to be true in order to execute the code after the guard. Unlike if, a guard statement always has an else clause, which is executed if the condition is not true.

func greet(person: [String: String]) {
        guard let name = person["name"] else {
                return
        }
        print("Hello \(name)!")
        guard let location = person["location"] else {
                print("I hope the weather is nice near you.")
                return
        }
        print("I hope the weather is nice in \(location).")
}
greet(person: ["name": "John"])  // Outputs "Hello John!"  // Outputs "I hope the weather is nice near you."
greet(person: ["name": "Jensen", "location": "Hefei"])
---
output: Hello John!
I hope the weather is nice near you.

If the guard condition is met, execution continues after the guard block. Optional binding in a guard condition protects the code after the guard.

If the condition is not met, the code in the else branch is executed. This branch must transfer control to exit the code block where the guard appears. You can use control transfer statements like return, break, continue, or throw, or call a non-returning function like fatalError().

Compared to using if for the same purpose, using guard improves readability. It allows code to execute sequentially without being nested in an else block, and lets you handle invalid cases close to the condition check.


Checking API Availability

Swift has built-in support for checking API availability, ensuring you don’t accidentally use unavailable APIs on the current deployment target.

The compiler uses SDK availability information to verify that all APIs used in your code are available for the specified deployment target. If you try to use an unavailable API, Swift will report a compile-time error.

Use an availability condition in an if or guard statement to conditionally execute code at runtime, checking if an API is available. The compiler uses the information from the availability condition to verify that APIs used in the block are available.

if #available(iOS 10, macOS 10.12, *) {
        // Use iOS 10 APIs on iOS, macOS 10.12 APIs on macOS
} else {
        // Use APIs for earlier versions of iOS and macOS
}

With the above availability condition, the if block runs only on iOS 10 or macOS 10.12 and later. The last parameter * is required, specifying that on all other platforms, if the version is higher than the minimum, the if block will execute.

In general, an availability condition uses a list of platform names and versions. Platform names can be iOS, macOS, watchOS, and tvOS. You can specify major versions like iOS 8 or macOS 10.10, or minor versions like iOS 11.2.6 or macOS 10.13.3.

/*
if #available(platform name version, ..., *) {
         APIs are available, statements will execute
 } else {
         APIs are unavailable, statements will not execute
 }
 */
Last updated on