Looping and Branching: Control Flow

Control flow refers to how we specify the order of how our Swift instructions are executed, or in a bigger picture, what parts of our code are to be run and under what conditions. In Swift, as in other languages, this is mostly implemented as conditionals and loops. Conditionals are statements that do or don’t execute code based on whether certain conditions are true or false at the time they’re evaluated. Loops build on conditionals by running some part of the code 0 or more times based on the conditions we provide.

Swift’s tools for control flow are probably very familiar to most developers, since many languages have if, for, while, and so forth. We’ll try them out now, so start a new playground called ControlFlowPlayground.

for Loops

Control flow also goes hand-in-hand with collections, which is why we’re reaching it now: once you have a collection of items, it’s natural to want to go through the collection and run some code on each item. We’ll start with going through an array with for.

1: let​ models = [​"iPhone 6s"​, ​"iPhone 6s Plus"​, ​"iPad Air 2"​,
2: "iPad mini"​, ​"iPad Pro"​]
3: for​ model ​in​ models {
4: NSLog​ (​"model: ​​(​model​)​​"​)
5: }

On lines 1--2, we create the models array, consisting of five strings. Line 3 is the for-in loop syntax, which says we want to go through every member of the models collection, and each time through, the item we’re working with will be represented with the local variable model.

The results pane doesn’t show us anything about what happened each time through the loop. To see that we’re actually doing something each time, we use the NSLog function on line 4 to write a message to the debug log. The output from NSLog isn’t shown by default; bring it up with View > Debug Area > Activate Console (C), or the middle button on the pane-switcher on the toolbar: images/startingswift/xcode-pane-buttons.png

Once the console pane is revealed, you should see the output of this loop:

 model: iPhone 6s
 model: iPhone 6s Plus
 model: iPad Air 2
 model: iPad mini
 model: iPad Pro

If it’s useful to have the index of members of the collection, we can do a loop that counts the members of the collection numerically, like this:

 for​ i ​in​ 0 ..< models.count {
 NSLog​ (​"model by index: ​​(​models[i]​)​​"​)
 }

This style of for loop creates a variable for the index (i in this case), and counts through a range of values. We create this with the range operator: ..<, which counts from the starting value (0) to one less than the ending value (models.count, the length of the array). If we wanted to include the ending value, we would use the range operator ... instead.

What if it would be convenient to have both the index and a local variable inside the loop? Sure, we could do let model = models[i] as the first line inside the loop, and then use that. However, Swift gives a much more elegant alternative, albeit one we’ll have to wait to discover in the next chapter.

if-else Statements

We often want to execute some statements only if certain conditions are true, and while the if statement is unfashionable in some coding circles, it’s familiar to nearly every programmer. Swift’s are simple enough, with one or two unique wrinkles. Let’s try an if statement that pulls a value out of a dictionary:

1: let​ sizeInMm = [
"iPhone 6s"​: 138.1,
"iPhone 6s Plus"​ : 158.1,
"iPad Air 2"​ : 240.0,
5: "iPad Pro"​ : 305.7]
let​ model = ​"iPhone 6s"
if​ sizeInMm[model] != ​nil​ {
NSLog​ (​"size of ​​(​model​)​​ is ​​(​sizeInMm[model]​)​​"​)
10: } ​else​ {
NSLog​ (​"couldn't find ​​(​model​)​​"​)
}

After creating the sizeInMm dictionary, we define the model key we are interested in, and then try to get its matching value from the dictionary. If the value is not nil, we execute the NSLog on line 9, and otherwise the NSLog on 11. Change the value of model to different values to see each block of the if-else log its message to the console.

The one truly interesting thing to say here is that the curly braces in Swift if-else statements are required, even if only a single line is to be executed in either case. This is different from the single-line behavior of C and Java, and eliminates easy-to-miss bugs caused by the inconsistent syntax of making the curly braces optional.

goto fail;

When Apple first announced that if statements in Swift would always require curly braces, a lot of us cheered and snarked that Apple had learned its lesson. Because just a few months earlier, missing curly braces hurt them badly.

In early 2014, Apple quietly updated its SSL implementation—used for any secure networking on iOS or OS X—and security researchers found that the earlier versions had a critical bug.

One part of the code needed to carry out a series of checks before calling an all-important sslRawVerify method. It basically looked something like this:

1: if​ ((err = FirstFunction()) != 0)
2: goto​ fail;
3: if​ ((err = SecondFunction()) != 0)
4: goto​ fail;
5: goto​ fail;
6: if​ ((err = ThirdFunction()) != 0)
7: goto​ fail;
8: 
9: err = sslRawVerify(...)

Without curly braces, a true if statement in C will execute one statement. So the if statement on line 3 executes line 4 if the SecondFunction test fails, which means we’re in an error state and call goto fail;, which means we never call sslRawVerify. And that’s fine; that’s what’s supposed to happen.

The problem is that, despite the indentation, line 5 is always called, regardless of what happened in the if statement. It looks like it’s part of the if, but it’s not, and the result is that sslRawVerify is never called, because line 5 always makes us goto fail;.

It’s a simple mistake, and lots of people missed it, but the loose syntax of C led to a critical security hole. It’s no wonder that when they designed Swift, Apple required curly braces on all if statements, to make sure this kind of bug was no longer possible!

Swift also offers a guard statement that is sort of like the opposite of if: it doesn’t have a curly-brace clause for the true case, just an else for when the condition is not true. We typically use these for early exits when we don’t want to run many lines of code if the condition isn’t met, and we don’t want to have to nest important code deeply in if-else indentation. Typically, guard statements perform early returns to bail out of code we don’t want to run, and we can’t do that kind of early return in a playground, so we’ll have to wait until we’re writing a real app to get our guard on.

switch Statements

The last kind of control flow technique we need to be aware of is switch. The switch keyword lets us test a variable against several possible values, and execute different code in each case. Let’s write a simple example:

 switch​ model {
 case​ ​"iPhone 6s Plus"​:
 NSLog​ (​"That's what I want"​)
 case​ ​"iPhone 7"​:
 NSLog​ (​"Have they even released that?"​)
 default​:
 NSLog​ (​"Not my thing"​)
 }

This switch will log "That’s what I want" if model is "iPhone 6s Plus", or "Have they even released that?" if it’s "iPhone 7", or "Not my thing" in all other cases.

If you’re familiar with C’s switch, you’ll be pleasantly surprised by one feature here: Swift’s switch works on Strings (or any type that can be evaluated with ==, actually), and not just on numeric types. Another improvement from other languages is that a matched case doesn’t fall through to the ones after it; in C, you would have to put a break at the end of the first case, or the code would execute the second case and the default as well.

One thing to be aware of is that switch statements must be exhaustive, meaning they must cover every possible value of the item being tested. Often, we use default as a catchall for this.

The switch statement gets heavily used in Swift because it’s the perfect way to deal with enumerations, which you’ll learn about in the next chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.57.52