Declaring the Symbols - Putting Expressions Together - Swift For Dummies (2015)

Swift For Dummies (2015)

Part III. Putting Expressions Together

Chapter 11. Declaring the Symbols

In This Chapter

arrow Using the symbol navigator

arrow Improving error-checking with assertions

arrow Exploring subscript expressions

arrow Recognizing and using patterns

For many developers, the project navigator in the navigator area is their preferred tool for navigating through a project (with the search navigator a close second). Of course, how you use Xcode is totally up to you.

Another navigator, however, is often useful: the symbol navigator, which lets you explore the symbols in your project both alphabetically and hierarchically. Basically, the project navigator lets you navigate by file and group, the search navigator helps you locate something when you know its name, and the symbol navigator looks at your project’s code structure. Yes, it’s possible to sit at your Mac and ask yourself, “Where in the world did I place that code sequence?” but the navigators can help eliminate those painful moments.

In addition to looking at the symbol navigator, this chapter provides information on a few other technologies that don’t fit easily into other chapters, such as Swift assertions, ranges, and patterns.

Navigating through Symbols with the Symbol Navigator

The project navigator gives you a view of your project using files and groups. The symbol navigator, on the other hand, looks at the logical structure. The most common symbols are classes; they typically contain properties (including variables) and methods (which in many contexts are known as functions). Here is the full list of symbols shown in the symbol navigator:

· Classes: The member properties and classes are shown. For more on classes, see Chapter 15.

· Properties: For more on properties, see Chapter 16.

· Protocols: For more on protocols, see Chapter 18.

· Functions: For more on functions, see Chapter 9.

· Structures: For a brief discussion of structures, see Chapter 9.

· Unions: For a brief discussion of unions, see Chapter 9.

· Enumerations: For more on enumerations, see Chapter 17.

· Types: For more on types, see Chapter 6.

· Globals: In common practice, globals are rarely used these days.

Choosing types of displays

In the figures that follow, you can see the project in a hierarchical order (Figures 11-1 and 11-2) or in an alphabetical one (Figure 11-3) using the buttons at the top of the navigator. Clicking on any symbol opens the relevant file in the editing area so that you can see its declaration.

image

Figure 11-1: You can use the symbol navigator.

image

Figure 11-2: Use the disclosure triangles to drill down (or up) on the symbols.

image

Figure 11-3: You can see the symbol navigator and a file side-by-side in the Assistant editor.

Note that this is the completed Locatapp project, so if you are following along and building your own copy, it has a few more symbols than you will have at this point.

The flat display (shown in Figure 11-3) is similar to the hierarchical display for many projects because, in the hierarchical display, at any level of the hierarchy the symbols are listed alphabetically.

Choosing what to display

The buttons at the bottom of the symbol navigator let you choose what to display. Each is a toggle to control if its data is displayed. From left to right, the buttons turn on (and off) the following displays:

· Class and protocol symbols only (button C): Global symbols are not displayed (these are the other symbols in the list shown at the beginning of this section — functions, structures, enumerations, and so forth).

· Project-defined symbols (document icon): Symbols from the frameworks aren’t shown.

· Members (button M): For the most part, these are the member properties and methods of classes or protocols.

In addition to the three buttons, a filter (search) field lets you further limit the symbols that are displayed.

icon tip I leave the middle button (project-defined symbols — the document icon) on and the other two off. I also leave the Classes and Protocols symbols expanded. That gives me the information I most frequently look at, but your choices and your projects may be different.

Preventing Disasters with Assertions

Everyone can agree that apps shouldn’t crash. But, as a wise manager once said, “Remember that people shouldn’t lie, cheat, or steal.” Yes, none of those things (including software crashes of all kinds) should happen — but they do.

Our job is to prevent such problems as much as possible.

One way to do this is to check in the code to see if a potentially problematic situation has arisen — perhaps a database is offline or a needed resource is nil. If the problem is found, you can gracefully end your app and write a message to the log, as well as inform the user.

This is the gold standard, but it’s also the most expensive approach, in terms of resources. It’s what should be done for the most extreme cases.

In Swift and other languages, assertions provide a less extensive (if less user-friendly) approach. An assertion (using the built-in global assert function), tests whether a condition is true or not. If it is true, processing continues, but if it is false, a message is provided to the user, and the app terminates then and there. (If you are running on a device or the iOS Simulator in debug mode, you can see exactly where the assertion failed.)

Assertions are often provided to catch issues during debugging when the terseness of the message is enough to let developers and testers know an area needs further attention. During the development process, it’s not uncommon to see a number of assert calls in a particularly problematic section of code.

An example of an assert statement is

assert (someValue<0, "someValue is out of range (<0)")

Patterns

In switch statements and other tests, you can match patterns instead of values. You may be used to switch statements that match on values, as in the following snippet (adapted for Swift syntax):

let val = 15
switch val {
case 15:
println (15)

case 20:
println (20)

default:
println ("Other result")
}

The value of val is set in the first line. The switch statement then tests for values of 15 and 20. Because Swift switches must be exhaustive, a default case is included so that the switch statement will execute some case no matter what value you set val to. Try it with several numeric values.

If you try changing the first line to a non-numeric value, however, you’ll get an error because the two case statements both expect a number. (That is a form of pattern matching.)

Now try another switch statement, such as the following:

let val = 20

switch val {
case let x:

println ("The value is \(x)")

default:
println ("Some other value")
}

Here, val is set to 20 as a value. However, note that you could also take the position that val is set to a single value. (In fact, both of these statements are correct — it’s set to an integer with the value of 20.)

The selection of the case to use doesn’t match the value of 20; rather, it matches the pattern of an integer. The case statement doesn’t consist of a value (as in the first example — 15 or 20). Instead, the case statement matches an expression, which, in this case, is

let x

By itself, x would be a value for the case statement; let x, on the other hand, is an expression. Because the case statement provides an expression, it assumes to match on the pattern of that expression rather than a value. The pattern of a single value matches let val = 20, so the casestatement executes. The case also succeeds for let x = "test" (still one value).

Now consider a more elaborate pattern by using a collection of values, as in this example:

let val = (1, 2, 3)

switch val {
case let (a, b, c):
println ("The values are: (\(a), \(b), \(c)).")

default:
println ("Some other value")
}

The pattern matches. Try changing the first line to this:

let val = (4, 5, 6)

It still matches. The pattern is three elements, so the following will also work:

let val = (1, "two", 3)

Add or remove one of the values, however, and you’ll get an error. The magic number is three. For example, neither of these will work:

let val = (1, 3)
let val = (1, 3, 4, 6)

it is the let expression rather than the value that makes this a pattern-matching switch statement.

Ranges

Ranges are useful when you are working with for-in statements. They come in two variations: closed and half-open.

A closed range used in a for-in statement looks like this:

for index in 1...5 {
//do something
}

Note the three dots in the range. This statement executes 5 times (experiment with it in a playground and you’ll see.)

A half-open range looks like this

for index in 1..<5 {
//do something
}

Instead of three dots, there are two dots and a <. If you experiment in a playground, you’ll see that it executes 4 times. Half-open ranges are useful for arrays that start at 0 because you want to step through them starting from 0 to one less than the count of elements in the array. (An array with 5 elements in it can start at 0 and go to 4 — not 5.)

You can combine ranges with switch statements as in the previous section. The following code works for a range of values:

let val = 4
switch val {
case 1..<5:
println ("in range")
default:
println ("not in range")
}

Experiment with changing the value of val as well as the value of the case statement. For example, with the code shown here, if you set val to 14 in the first line, it is not in range. Try experimenting with a range like "abc". . ."def" and see what you get.

icon tip The half-open range only works with ..<. Any character other than < at the end is an error. If you want a different comparison, reverse the sign on the expression or otherwise make it comport with .. < .