Controlling the Flow - Introducing Actions - Swift For Dummies (2015)

Swift For Dummies (2015)

Part II. Introducing Actions

Chapter 8. Controlling the Flow

In This Chapter

arrow Redirecting the flow of Swift’s code with flow controls

arrow Using for and while loops

arrow Working with conditions

Code is executed sequentially — line after line of code. That sequential flow is interrupted in two ways:

· Through the use of flow controls — code structures that modify the sequential flow of code. Usually the term flow controls refers to conditional statements and various types of loops. Occasionally they explicitly change the flow by forcing the app to jump out of the current function either to a specific command or, more frequently, to whatever it was that called the function. All of these force the “next” line of code to be something other than the next line of code in the source file.

· By functions and closures — sections of code implemented in response to certain conditions. Those conditions can be events, such as the completion of an asynchronous process, or they can be references within the code that fire off synchronous or asynchronous processes. All of these disrupt the next-line structure to start processing in response to an event.

These concepts are implemented in different ways in different languages. This chapter gives you the basics of flow controls in Swift.

Looping through Code

No matter which programming languages you know (and you do know at least one, don't you?) you’ll have encountered loops. Swift also supports loops, and, as you'll see, Swift’s loops include variations and simplifications not found in other languages.

There are two basic types of loops: for loops, which include some kind of counter, and while loops, which rely on specified conditions for their continued operation.

There’s another form of loop that you may have used: a polling loop. This loop repeatedly checks (or polls) a condition, such as whether the user’s mouse button is still down or whether a communications link is still active. You can implement a polling loop in Swift with awhile loop — but don't. Polling loops are very expensive: In order to repeatedly check their condition, they tie up the computer's resources. With a message-based operating system such as Cocoa or Cocoa Touch, you can observe the event or condition and be notified only when its state changes. In this way, the operating system and the framework do the polling for you. You'll find more on this topic in Chapter 18, “Using Protocols to Provide Templates for Functionality.”

Using for loops

For loops are one of the most basic programming structures: They let you execute a section of code repeatedly based on some type of control (which is the for in a for loop).

Swift supports two types of for loops:

· for-in loops

· for-condition-increment loops

These are discussed in the next few sections.

These loops aren’t new: for-condition-increment loops have been around since the dawn of computer languages, whereas for-in loops (sometimes called iterators) are closely intertwined with object-oriented programming and were first seen in the 1970s. Even if you’ve been programming for a while, for-in loops may be new to you. In the sections that follow, I discuss for-in loops at more length than other loops — partly because they’re somewhat rare, and partly because some Swift developers find that they use them more than the other types of loops.

Working with for-in loops

Although they may be new to you, for-in loops are not unique to Swift. They’re used in other object-oriented programming languages (such as Python, Scala, and C++), and are particularly useful in working with collections of items — as opposed to indexed values in arrays.

Objects indexed in an array have a clear, unambiguous sequence. (Two elements can't have the exact same index). If you want to construct a loop that processes each array element, then using a for loop to work with indexes is a great approach, as I discuss in the next section, “Working with for-condition-increment loops.”

In collections of objects or other data elements, however, the contained elements may be unindexed and in no obvious order. In these cases, the elements are members of some collection or other, and are identified individually through means other than index numbers. In fact, in some collections, identifying individual items is not necessary or even impossible. For these collections, all you may know is it’s a collection, and that it has elements.

icon tip For more information about collections, see Chapter 7, “Collecting Objects.”

Scanning through an array in undefined order can be a nuisance, but in a large number of cases, the sequence of presentation is irrelevant: You simply want to deal with each bank account, each appointment, each bill, each friend, and so forth. As long as you access each element, and omit none, everything is fine.

The object behind the Swift syntax for a for-in loop relies on two protocols: Sequence and Generator. Generator contains a next function, which is the heart of the matter. If you have a class or structure that adopts the Sequence protocol (and through that, adopts Generator), you can access the next function that can be used by an iterator. Whether next is interpreted to be random or ordered depends on the class or structure that contains it. The interpretation of next, in other words, is dependent on the class or structure: It may be a random next element or it may be an ordered next element. From the high-level Swift perspective, next is undefined except that it provides an element according to the rules of the structure or class that is considered to be “next.” In life, we usually pair “next” with some context such as “next student in line,” “next meeting,” or “next week.” In writing your code, it is usually safest to assume that there is no predetermined order unless you are certain that there is one.

The Swift syntax for for-in loops is

for < item > in < collection > {
< statements >
}

Note that all italicized terms (and the < and > characters) are general descriptions of missing syntax elements. They aren’t intended to be seen as part of the syntax itself, or to be typed verbatim. In the syntax above, these terms are:

· <item>: This refers to an element of a collection. It contains a different element each time the loop is evaluated.

· <collection>: This can be any structure or class that conforms to the Sequence protocol. Don't bother looking it up in the Swift documentation, however: Most of the time you'll just use a collection object, as I discuss in Chapter 7. You can also use a range or an array here. For more on ranges, see Chapter 5.

· <statements>: These refer to Swift code statements, and can include more or less anything you want (and as many statements as you want).

Why are there so few for loops in frameworks and templates?

A great way to learn Swift (or Objective-C, for that matter) is to create new projects from the built-in Xcode templates. Just create a project, build it, and run it in iOS Simulator (or directly on your Mac). Explore the code and watch how it functions. If you do this and then use the Search navigator to find instances of the word for in the source code, you may be surprised by the results. You'll find for in comments — as in, say, part of the word before — and you'll find it in named parameters such as forKey, but you won't often find it in actual for-in loops (or in other loops) in the templates.

The absence of for loops here reflects the role of the frameworks and, to a lesser extent, the templates. Frameworks provide your app’s overall structure. They typically take care of the user interface and the basics of user interaction, and it’s here, among these processes, that loops often occur. Generally, you’ll only be concerned with overriding classes for specific data elements or functions. This sort of management happens in the framework, often by looping, and in most cases you provide these loops’ internal functionality. In other words, the loop and other UI code control the flow, but your implementation of a specific class provides the detailed functionality.

This means that a large part of your app’s flow control is already implemented for you in Cocoa and Cocoa Touch. Although you need a basic understanding of loops and the ways control works in an app, most of the time you should be focused on other matters — namely, the details of your implementations of functionality that take place underneath the top-level flow control. If you're tempted to write loops to work through every window or every button or every, well, anything — give it a second thought. This is typically the framework’s job, and your effort is probably better spent elsewhere.

The following steps build code on which you experiment with a for-in loop. It will be shown later in Figure 8-1 and Listing 8-1 with some additions that will be added later. As you have seen in other chapters, start by creating a playground using Swift for either iOS or OS X.

image

Figure 8-1: Using a for-in loop.

1. Declare a collection for the for-in loop to use.

Make this declaration by using a statement such as the following:

var elements = [1, 3, 5, 17, -1]

In this case, the declared collection is a variable, but it could be a constant or any other type of collection.

2. Create the for-in loop.

Here’s an example:

for myElement in elements {
// do something with myElement
}

Note that this is the loop’s declaration. You don't need to add a separate declaration for myElement. In fact, doing so declares a separate variable.

The loop uses the collection you declared or referenced in Step 1. Here myElement is filled with each value of the loop; you can use it inside the loop.

icon tip You can do whatever you want with the iterated element inside a loop.

Here are some variations on for-in loops.

· Performing a test on the item: Often you perform a test on item, but you may also use it as part of an operation. Sometimes you do both. As an example, the following code, placed within the loop, tests whether the value of the iterated element is a negative number; if it is, it prints out that value.

if myElement < 0 {
println ( " \(myElement.description) is a negative
number")
}

· Breaking out of the loop: If you’re searching a collection for the highest or lowest numeric value, such as the highest game score, you’ll have to search all the values in the collection, so this variation won’t help you. On the other hand, if you’re trying to find a specific value, or a value that fulfills certain conditions, this one’s for you. When doing such a search, you’ll want to stop searching the moment you’ve found the value you’re looking for. To terminate the loop at that point, just add a break statement. This statement drops you out of the loop and executes the next statement beyond it, as shown here:

if myElement < 0 {
println ( " \(myElement.description)is a negative
number")
break;
}

· Identifying the item in each iteration: As you may recall, the item in a for-in loop contains the element from the collection. This element may be a value, as shown in the code here, but it can just as easily be an object. In the case of an ordered collection, such as an array, you may want to know the element's index number. Because this information isn’t part of the for-in loop, you’ll need to calculate it.

icon tip See the section, “Working with for-condition-increment loops,” later in this chapter, to see another way of getting this information.

To perform this calculation, you must first create a counter. This counter creates a unique value for each element within the loop. To create one, follow these steps (refer to Figure 8-1 or Listing 8-1):

1. Create a counter variable outside the loop.

Make certain this is a variable declared with var, not a constant declared with let. Use a statement such as the following:

var index = 0

2. Inside the loop, increment the counter.

index++

Now the body of your loop may look like this:

var index = 0
if myElement < 0 {
println ( " \(myElement.description)at \(index) is a negative number")
}
index++

icon remember Before adding your counter, decide how you want to number the elements in the for-in loop. The index values in an array begin at 0, but depending on your needs, you may want to set your counter value to start at 1 or any other number. In the first step above, set your initial counter/index number. If you want the first element that passes through the loop to be 0, place your increment statement at the end of the loop. This ensures that your counter will remain 0 the first time through. However, at the end of the first iteration, the counter’s value will be 1, which will be the value used during the second iteration (that is, for the second value).

If you want to start at 1 right away, however, set your counter variable to 1 when you create it. (Or, alternatively, set it to 0 and increment it at the beginning of the loop.)

· Ignoring the item: Sometimes, you don't need to do anything to the item returned from each iteration. In that case, you can replace it with an underscore character, as in the following:

for _ in elements {

In this case, if you find you need to reference the item later, use the technique described in the previous bullet to identify it.

· Iterating over a dictionary: You can iterate over a dictionary in exactly the same way that you iterate over any other collection. The only difference is that the item in the syntax (that is, each item that’s passed to the loop as it processes) is a tuple consisting of the key and value for a dictionary element. Thus, a for-in loop for a dictionary will look like this:

for (myKey, myValue) in myDictionary {
//do something with myKey and myValue
}

Figure 8-1 and Listing 8-1 show the code as it may be at this point (not all of the options discussed are shown in the code).

Listing 8-1: Using a for-in Loop to Manage a Collection

// Playground - noun: a place where people can play

var elements = [1, 3, 5, 17, -1]

var index = 0;

for myElement in elements {

// do something with myElement
if myElement < 0 {
println ( " \(myElement.description) at
\(index.description) is a negative number")
}

index++
}

Working with for-condition-increment loops

Now we can turn our attention to for-condition-increment loops, which are the classic for statements you may have seen in C and other legacy programming languages. Swift’s way of handling these loops is more or less what you might expect — with one possible exception: The loop control does not need to be enclosed in parentheses. It can be, that is, but it need not be.

The Swift syntax for for-condition-increment loops is:

for < initialization >; < condition> ; < increment> {
< statements >
}
for < initialization >; < condition> ; < increment> {
< statements >
}

Here are the non-syntax components of the syntax (remember the < and > are not part of the syntax or component):

· <initialization>: Initializes the index counter. The index counter must be declared before it is used.

· <condition>: Specifies the condition according to which the counter continues to be incremented. This condition is checked during every pass through the loop, and the counter is incremented (by increment, discussed next) until condition fails. When condition fails, the for-condition-increment loop terminates.

· <increment>: Specifies the expression to use to increment the index counter. A common increment is myCounter ++, which increments the counter by 1, but you can use any increment you want — including fractions, negative numbers, and even expressions.

· <statements>: These refer to Swift code statements, and can include more or less anything you want (and as many statements as you want).

Listing 8-2 recasts the loop from Listing 8-1 using a for-condition-increment loop. Note the fourth line of code: You might expect this line to appear with parentheses, as follows:

for (myElementCounter=0; myElementCounter <5;
myElementCounter ++) {

You can use the parentheses, but you don't have to. Do whatever is easiest for you and the people you work with. ( Remember that you're usually writing for other people on your team — either today or in the future.) Whatever you do, try to be consistent.

Listing 8-2: Using a for-increment Loop

// Playground - noun: a place where people can play

var elements = [1, 3, 5, 17, -1]

var myElementCounter = 0;

for myElementCounter =0; myElementCounter <5;
myElementCounter ++ {

// do something with myElement
if elements[myElementCounter] > 15 {
println ( " \(elements[myElementCounter].description)
at \( myElementCounter.description) is greater than
15")
}
}

Here are the steps to create a for-condition-increment loop like the one in Listing 8-2. Begin with a new playground set up for iOS or OS X.

1. Declare a collection for the for-condition-increment loop to use.

You can either create a new collection or use the collection (elements) defined in the previous section:

var elements = [1, 3, 5, 17, -1]

2. Create and initialize a variable to use as a counter, such as myElementCounter .

As you may recall, this step is unnecessary with basic for-in loops, whose “counter” is implicitly declared in the for statement. (Incremented counters aren’t used in for-in loops; instead, each element in the collection is returned one-by-one.) If you need to be able to identify the n-th element of the collection, you will need to calculate your own index with a for-in loop just as you are doing here.

3. Set up the loop control.

Here’s where you write the for statement. Make sure to initialize your counter variable correctly and to set the condition and increment according to your needs. An example:

for myElementCounter =0; myElementCounter <5;
myElementCounter++ {

4. Inside the loop, process each element.

Instead of simply getting the element as part of the for statement, as in for-in loops, with for-condition-increment loops, you must subscript the array using the counter (see Figure 8-2):

// do something with myElement
if elements[myElementCounter] > 15 {
println ( "
\(elements[myElementCounter].description)
at \( myElementCounter.description) is a negative
number")
}

If you think this makes for-in loops simpler than for-condition-increment loops, well, you’re right.

image

Figure 8-2: Using a for-condition-increment loop.

Using while loops

As is the case in other languages, Swift supports while loops. In for-condition-increment loops, the syntax requires both a condition and an increment, as you may recall. However, in while loops, the basic syntax requires only a condition. Here's a simple while loop:

while myValue < 10 {
// do something
]

In this loop, myValue is the condition. Keep in mind, however, that you must eventually change the value of myValue. If myValue remains unchanged while the loop is executing, the loop is either an infinite loop or one that doesn't execute at all.

Note, though, that the condition in a while loop also can be an expression, so at times a while loop can closely resemble a for-condition-increment loop, as in the following (see Figure 8-3):

var myValue = 5
while myValue++ < 10 {
// do something
]

image

Figure 8-3: Using a while loop.

The three components of a for-condition-increment loop are present here — the declaration of myValue and its initial value (remember, Swift requires initialization); the condition (< 10); and the increment (myValue ++).

While loops have two formats:

· do <action>-while <condition>: With this format, the action is always performed at least once. The condition is evaluated only after the action is performed, and if found true, the action is performed again (possibly even more times).

· while-<condition>-do <action>: Here the condition is evaluated first. If it’s true, the action is performed. The loop repeats by performing the action and then re-evaluating the condition.

Both forms of while loops are similar to those in other languages. The one slight difference is that the condition in other languages may need to be placed in parentheses.

Using Conditions

The one-thing-after-another structure of most code is interrupted in a number of ways. The most basic way is to use conditions — in Swift, as in most other programming languages, this means using if or switch statements.

if statements in Swift are very similar to those in other languages, whereas switches have significant variations.

Cocoa and Cocoa Touch, together with the Darwin core on which OS X and iOS rest, support sophisticated messaging. Messaging allows non-linear processing — a critical component of iOS and OS X. With messaging, you set up your app or sections of it to receive notifications rather than periodically (or constantly!) checking to see the status of something. This tends to reduce the use of conditional statements.

Working with if statements

In Swift, if statements are easy to discuss because they’re used in a way that’s similar to the way other languages use them. Here’s the Swift syntax for a simple if statement:

if < condition > {
< statements >
}

Similarly, a compound if statement (sometimes called an if-else statement) has two branches:

if < condition > {
< statements for true >
} else {
< statements for false >
}

The biggest difference between the way Swift and most other languages use if statements is that, in Swift, the condition doesn’t have to be placed in parentheses. It can be, but it’s not required.

Another difference: in Swift, condition can be a Boolean expression, or an expression or variable of any type that conforms to the BooleanType protocol. This difference isn’t obvious from the syntax examples, but it’s a very important aspect of Swift. It’s an example of how a protocol can allow a type to be of one type (or a subclass of one type) and also conform to a protocol which in some ways has the appearance of a class. Thus, a class can combine features of itself or its superclass(es) with the methods of a protocol. (See Chapter 18 for an in-depth discussion of protocols.)

Working with switches

Here’s the formal syntax of switch statements in Swift:

switch < control expression > {
case < pattern 1 >:
< statements >
case < pattern 2 > where <condition>:
< statements >
case < pattern 3> where < condition ,
< pattern 4 > where condition >:
< statements >
default:
< statements >
}

Figure 8-4 shows a switch statement in a playground. Note that the spacing in Figure 8-4 is different from the spacing shown in this chapter. This doesn't matter from the standpoint of syntax, but it does matter in making your code readable. Choose whatever style you like (and set your Xcode preferences to match in the Text Edit tab of Preferences).

image

Figure 8-4: Using a Swift switch.

Swift switch statements have some significant differences from switch statements in other languages. A list of the differences suggests the problems that people have had over the years with C-style switch statements. These include the problems that are solved with the following Swift features:

· Exhaustivity: A switch statement in Swift must cover every possible value for the control expression. Before you tear your hair out, remember that you can accomplish that goal by always including a default case.

· Falling through: Just about every programmer has experienced the “joy” of falling through from one case statement to the next. In many other languages, a break statement must be included at the end of a case statement; if it isn’t, control passes to the next case statement. With Swift, after you move into a case statement and it executes, control passes to the first statement after the switch statement.

· Guard clause: As in other languages, a switch statement contains one or more case statements. In Swift, you can add a guard clause, which begins with where. This allows you to have several case statements that match a single control expression value and to further refine their structure with the guard clause. A guard clause is similar to an AND statement in a query in that it refines the basic case statement with additional considerations.

In Figure 8-4, you see an example of a guard clause:

case "sample text" where guardExpression == "guard":

This case statement is executed on in the case where the control expression at the top of the switch statement is equal to “sample text” and another variable called guardExpression is equal to “guard.” You could also have additional case statements, such as the following:

case "sample text" where guardExpression == "guard 1":
case "sample text" where guardExpression == "guard 2":
case "sample text" where guardExpression == "guard 3":

icon tip A guard clause often eliminates the need to place an inner switch statement inside a case statement.

Transferring Control

A few other control transfer statements exist in Swift, such as those in the following list. Note that the first two are similar to statements in other languages:

· continue: You can use the continue statement in any of the loop statements discussed in this chapter. It ends that particular iteration of the loop and proceeds to the next one. Any further statements within that iteration are not performed.

· break: A break statement terminates a loop iteration or the current case of a switch statement. Control transfers to the first statement after the switch statement or loop.

· fallthrough: This statement explicitly duplicates the behavior of case statements inside switch statements in other languages. When placed within a case statement, it transfers control to the next case statement in the switch statement.

Many people believe that relying on this type of structure is dangerous, and they avoid using this technique.

Using Assertions

Assertions aren’t new in software development. The first known reference to them is from a talk given by Alan Turing in 1949. Turing’s point, which is still valid today, is that it is not practical to thoroughly check every single decision point in a program. The combinations of choices in conditional statements (much less the variations in data and hardware conditions) quickly make the number of combinations and permutations enormous. Although we do attempt to check every decision point and design assumption, assertions are a powerful tool for keeping an app running properly.

An assertion simply presents a condition in the form of a Boolean expression that must be true. Regardless of the programming path that has been taken (which may contain design or implementation errors), without regard to the data (which may contain user errors), and without considering hardware and network connections that may act in erratic and sometimes non-repeatable ways, a certain condition must be true (or false) if the app is to continue.

In the development and debugging phases of app development, assertions can help you work back to find logical flaws. These assertions may be removed (or, more commonly, commented out) in the production versions of the app. Other assertions may remain in a shipping app to catch serious problems.

Assertions may or may not display an error message to the log (they typically do), and they cause the app to terminate. This is a pretty dramatic step, but it is invaluable during debugging, and may be needed in a shipping app because in the case of an erratic error, the message may be able to guide you (and users) to the true source of the problem.

The global function assert is used to implement an assertion. It takes two parameters: a Boolean expression and an optional string, as in:

assert (idNumber == 0, "missing ID number")

Note that this is an error condition that probably could be managed in validation routines for data entry. However, if you are far from data entry in your app and must assume that all of those validation routines have already been passed, this assertion would catch a deliberate deletion of the value by a line of code that is wrong.

icon tip Wherever possible, use standard error-checking routines and other techniques instead of assertions.