LEARN SWIFT

Buy the PDF

9.1 if

Here’s an example of using an if statement in Swift. The condition can only contain operators that returns a boolean value.

let capacity = 10
var reservations = 10

if(reservations >= capacity){
    println("Sorry, the inn is full")
}

Here’s the syntax for a second condition and a default catch-all condition:

if(reservations >= capacity){
    println("Sorry, the inn is full")
}else if((capacity - reservations) <= 1) {
    println("only 1 place left!")
} else{
    print("plenty of room at the inn")
}

Swift also has the if let statement which is used to handle optionals and do something if and only if the optional has a value. We’ll cover if let when we talk about optionals in chapter 10.

9.2 Loops

9.2.1 for and for-in

Swift has a standard for loop that is similar to the kind you would encounter in c, java etc. It takes a variable, a condition, and an increment. For example, here we print out every 2nd number in a sequence using a var i. This loop starts with i set to 0 and terminates when i exceeds the number of elements in the array, and increments i by 2 each time it executes the loop.

let numbers = [1, 2, 3, 4, 5, 6]

for var i=0; i<numbers.count; i=i+2 {
    println("\(numbers[i])")
}

Swift’s for-in loop is more akin to iterators in Ruby/Python. You can use it to iterate through each element in a sequence.

let numbers = [1, 2, 3, 4, 5, 6]

for num in numbers{
    println(num)
}

This example shows how to use a for-in loop to iterate over every element in a sequence.

In Swift, functions are first class objects, and there is strong support for closures. So we can also adopt the common patterns that you’ll encounter in functional languages of applying a closure to a sequence. For example we can apply a function to every element in a sequence and collect the results using built in functions such as map and reduce. We’ll get into that in more detail in chapter 11.

9.2.2 while

A while loop will keep executing until the condition is false. It evaluates the condition before executing the loop, so it can execute 0 or more times.

let numbers = [1, 2, 3, 4, 5, 6]
var i = 0
while i < (numbers.count / 2) {
    println("\(numbers[i])")
    i++
}

9.2.3 do…while

do...while evaluates the condition after running through the loop. So in this case the body of the loop will be executed at least once. (I’ve simplified the conditional to just true/false in the examples below to show that the loop runs once even though the condition terminates the first time that it’s checked.

var i2 = 0
do {
    println("\(numbers[i2])")
    i2++
} while false

Even though the condition terminates the loop the first time it’s checked here, the loop body runs once before that check takes place.

9.3 switch

Swift’s switch statement is analogous to switch or case statements in many other languages. But switch in Swift supports more complex behaviour so you’ll find that in Swift you will use the switch statement a lot more that you may be accustomed to.

Here’s an example of a simple switch statement:

 1 let capacity = 10
 2 var reservations = 10
 3 
 4 switch (capacity - reservations) {
 5 case 0:
 6     println("Sorry, the inn is full")
 7 case 1:
 8     println("Only 1 place left")
 9 case 2, 3, 4:
10     println("Getting close to full now")
11     // switch must be exhaustive, default clause needed
12 default:
13     println("plenty or room at the inn")
14 }

In the above example we match our cases against the difference between capacity and reservations. Each case can match a single value (line 5, 7) or multiple possible values (line 9). There is a default case that executes when none of the preceding cases match (line 12). The default condition is usually required. The set of conditions for a switch statement must be exhaustive - your set of cases must cover all possible values of whatever you are matching against. Thus in the example above, if you remove the default clause, you’ll get a compiler error. You need to either have a default clause, or ensure that the cases are exhaustive.

The switch statement in Swift supports some interesting use of pattern matching. This makes it a much more useful that the simple switch statement outlined above and you will probably find yourself using it a lot more than you that you do in other languages. Lets take a look at a pattern matching example.

The switch statement takes a value and compares it against a set of defined patterns. It then executes the block of code associated with the first pattern that matches the value. It will only execute the first matching branch in a switch statement (i.e. if more than one branch matches, it will not continue through the switch after executing the first branch and execute the second matching branch too - the switch statement returns after executing the branch associated with the first match)1.

In our previous example, the pattern matching involved consists of a simple equality check i.e. we check the value of (capacity - reservations) against the value given in each of the case branches. So we check if the value of (capacity - reservations) equals 0, or equals 1 or equals 2, 3 or 4. And if it isn’t equal to any of them we execute the default branch.

But we can do more than simple equality checks. We can also match against a range, or a tuple, or an enumeration 2 using Swifts pattern matching functionality. In fact combining Swift’s pattern matching functionality with a switch statement is a pattern that we’ll use regularly for handling errors and processing the results of functions.

Lets look at some other examples of how we can match cases in switch statements.

We can match against a range. For example, we could rewrite the third case of our previous example using a range.

 1 switch (capacity - reservations) {
 2 case 0:
 3     println("Sorry, the inn is full")
 4 case 1:
 5     println("Only 1 place left")
 6 case 2..<5:
 7     println("Getting close to full now")
 8     // switch must be exhaustive, default clause needed
 9 default:
10     println("plenty or room at the inn")
11 }

Things start to get more interesting when we start to match against tuples and enums.

Lets revisit our previous example where we represented responses using a code and message, which we placed in a tuple. We can match our value against the tuple. In this example, we match against the code part of the tuple, and we don’t care about the value of the message so we match against any message using _.

let response = (200, "OK")
switch response{
case (200, _):
    println("Looks good")
case (500, _):
    println("You got an error")
default:
    println("Unrecognized response code")
}

We can extend this to include a range and match the range against the tuple. For example, http status codes are grouped according to the first digit of the code.3. E.g. Codes 2xx indicate success, 3xx redirection, 4xx errors etc. We can extend the above to capture error and success codes:

switch response{
case (200..<300, _):
    println("Looks good")
case (500..<600, _):
    println("You got an error")
default:
    println("Unrecognized response code")
}

Now our switch statement treats all the 5xx codes as errors and all the 2xx codes as success.

We may want to do some extra processing on the value that we matched within the branch of a switch statement. We can use let to assign a temporary name to the match. Lets say we cared about the value of message for 5xx codes. We can capture the value of message using let msg in the match for that branch of the switch statement as shown below:4

switch response{
case (200..<300, _):
    println("Looks good")
case (500..<600, let msg):
    println("You got an error: \(msg)")
default:
    println("Unrecognized response code")
}

Now we can print the error message when we get an error and ignore it otherwise.

We can also add extra conditions to our match by adding a where clause. For the sake of continuing the above example, lets say our system only accepts even success status codes, and displays a warning for odd success status codes. We can do that by adding a where clause.

 1 let response2 = (code: 203, message: "Non-Authoritative Information")
 2 switch response2{
 3 case (200..<300, _) where (response2.code % 2) == 0:
 4     println("Looks good")
 5 case (200..<300, _) where (response2.code % 2) == 1:
 6     println("Warning: Stop using odd response codes")
 7 case (500..<600, let msg):
 8     println("You got an error: \(msg)")
 9 default:
10     println("Unrecognized response code")
11 }

In this example, we treat even success codes differently to odd success codes by providing additional matching conditions using the where clauses at lines 3 and 5.

We can also match a value against an Enumerable. This is an important topic and I’m not going to broach it here. We’ll cover this in more detail in our chapters on Enumerations, Functions and Error Handling.

9.4 Altering control

Swift has a few of ways of modifying control in a loop:

  • break terminates the current loop.
  • continue jumps to the next iteration of the current loop (i.e. next)
  • fallthrough is used in switch statements to fallthrough to the next branch in the switch. So you can use this to force more than one branch of a switch statement to execute.

9.4.1 guard

The guard statement was introduced in Swift 2 and it lets you check a condition and exit if it’s not true. In contrast to an if statement, which runs it’s body if some boolean condition is true, the guard statement body only runs it’s body if its condition is false.

So it’s a bit like an assert but you have more control over what happens when the condition isn’t met and the guard statement gets triggered.

func printEvenPostiveNumber(num: Int){
    guard num > 0 else {
        print("\(num) is not > 0")
        return
    }
    guard num % 2 == 0 else {
        print("\(num) is not even")
        return
    }
    print("\(num) is positive and even")
		// do something
}

printEvenPostiveNumber(-1) // "-1 is not > 0\n"
printEvenPostiveNumber(3)  // "3 is not even\n"
printEvenPostiveNumber(6)  // "6 is positive and even\n"

Here’s a function that should only deal with positive even numbers. In this example we have 2 guard statements that check if the argument passed to the function is positive and even. If the argument fails either of those checks the guard statement gets triggered and our function returns early. Only positive, even numbers make it through the 2 guard clauses.

9.4.2 Labeled statements

Labeled statements allow you to attach a label to a loop or switch statement, and then you can use that label with a break or continue statement to stop or continue execution of the labelled statement. I would be cautious about using these as they remind me a bit of goto5. There may be cases (e.g. long nested loops, where the addition of labels adds clarity) where they are the best way of encoding your program logic, but I think they should probably a last resort, used for cases where other techniques have been exhausted. But they do allow for customizable looping behaviour. You can provide a label for any looping construct in Swift and then you can send that label to a break or continue statement cause control to jump to the labelled loop.

  1. Matching only the first matching case in a switch statement is the default behaviour. However you can change this behaviour using fallthrough. This will indicate to swift that if it matches a case, it should continue trying to match the following cases in the switch statement. Thus you can setup a switch statement where you can match multiple branches. 
  2. Pattern matching against an enum using switch is a common pattern in swift. We’ll see later how this pattern is used to handle return values from functions, and especially how it is used to handlers errors. 
  3. http://en.wikipedia.org/wiki/List_of_HTTP_status_codes 
  4. You could also do this by providing a name for the fields of the response and then accessing it directly in the body of the switch statement, similar to the next example. 
  5. http://www.u.arizona.edu/\( \sim \)rubinson/copyright_violations/Go_To_Considered_Harmful.html