What Is a Switch? if/else statements execute code based on whether the condition under consideration evaluates to true. In contrast, switch statements consider a particular value and attempt to match it against a number of cases. If there is a match, the switch executes the code associated with that case. Here is the basic syntax of a switch statement: switch aValue { case someValueToCompare: // Do something to respond case anotherValueToCompare: // Do something to respond default: // Do something when there are no matches } In the example above, the switch only compares against two cases, but a switch statement can include any number of cases. If aValue matches any of the comparison cases, then the body of that case will be executed. Notice the use of the default case. It is executed when the comparison value does not match any of the cases. The default case is not mandatory. However, it is mandatory for switch statements to have a case for every value of the type being checked. So it is often efficient to use the default case rather than providing a specific case for every value in the type. As you might guess, in order for the comparisons to be possible the type in each of the cases must match the type being compared against. In other words, aValue type must match the types of someValueToCompare and anotherValueToCompare. This code shows the basic syntax of a switch statement, but it is not completely well formed. In fact, this switch statement would cause a compile-time error. Why? If you are curious, type it into a playground and see. Give aValue and all of the cases values. You should see an error for each of the cases, telling you "'case' label in a 'switch' should have at least one executable statement." The problem is that every case must have at least one executable line of code associated with it. This is the purpose of a switch statement: Each case should represent a separate branch of execution. In the example, the cases only have comments under them. Because comments are not executable, the switch statement does not meet this requirement. Switch It Up Create a new playground called Switch and set up a switch. Listing 5.1 Your first switch import Cocoa var statusCode: Int = 404 var statusMessage: String switch statusCode { case 400: statusMessage = "Bad request" case 401: statusMessage = "Unauthorized" case 403: statusMessage = "Forbidden" case 404: statusMessage = "Not found" default: statusMessage = "None" } The switch statement above compares an HTTP status code against four cases in order to match a String instance describing the error. Because case 404 matches statusCode, statusMessage is assigned to be equal to "Not found", as you can see in the sidebar (Figure 5.1). Try changing the value of statusCode to see the other results. When you are done, set it back to 404. Figure 5.1 Matching an error string to a status code Suppose you want to use a switch statement to build up a meaningful error description. Update your code as shown. Listing 5.2 Switch cases can have multiple values import Cocoa var statusCode: Int = 404 var statusMessage: String = "The request failed:" switch statusCode { case 400: statusMessage = "Bad request" case 401: statusMessage = "Unauthorized" case 403: statusMessage = "Forbidden" case 404: statusMessage = "Not found" default: statusMessage = "None" case 400, 401, 403, 404: statusMessage = "There was something wrong with the request." Fallthrough default: statusMessage += " Please review the request and try again." } There is now only one case for all of the error status codes (which are listed and separated by commas). If the statusCode matches any of the values in the case, the text "There was something wrong with the request." is given to the statusMessage. You have also added a control transfer statement called fallthrough. Control transfer statements allow you to modify the order of execution in some control flow. These statements transfer control from one chunk of code to another. You will see another way to use control transfer statements in Chapter 6 on looping. Here, fallthrough tells the switch statement to  the bottom of a case to the next one. If a matching case has a fallthrough control transfer statement at the end of it, it will first execute its code, and then transfer control to the case immediately below. That case will execute its code whether or not it matches the value being checked against. If it also has a fallthrough statement at the end, it will hand off control to the case below, and so on. fallthrough statements allow you to enter a case and execute its code without having to match against it. In this example, because of the fallthrough statement, the switch statement does not stop, even though the first case matches. Instead, it proceeds to the default case. Without the fallthrough keyword, the switch statement would have ended execution after the first match. The use of fallthrough in this example allows you to build up statusMessage without having to use strange logic that would guarantee that the comparison value matched all of the cases of interest. The default case uses a compound-assignment operator (+=) to add a recommendation to review the request to the statusMessage. The end result of this switch statement is that statusMessage is set to "There was something wrong with the request. Please review the request and try again." If the status code provided had not matched the values in the case, the end result would have been statusMessage being set to "The request failed: Please review the request and try again." If you are familiar with other languages like C or Objective-C, you will see that Swift switch statement works differently. switch statements in those languages automatically fall through their cases. Those languages require a break control transfer statement at the end of the switch works in the opposite manner. If you match on a case, the case executes its code and the switch stops running. Ranges You have seen switch statements in which the cases have a single value to check against the comparison value and others in which the cases have multiple values. switch statements can also compare to a range of values using the syntax valueX...valueY. Update your code to see this in action. Listing 5.3 Switch cases can have single values, multiple values, or ranges of values import Cocoa var statusCode: Int = 404 var statusMessage: String = "The request failed with the error:" switch statusCode { case 400, 401, 403, 404: statusMessage += " There was something wrong with the request." fallthrough default: statusMessage += " Please review the request and try again." } switch statusCode { case 100, 101: statusMessage += " Informational, 1xx." case 204: statusMessage += " Successful but no content, 204." case 300...307: statusMessage += " Redirection, 3xx." case 400...417: statusMessage += " Client error, 4xx." case 500...505: statusMessage += " Server error, 5xx." default: statusMessage = "Unknown. Please review the request and try again." } The switch statement above takes advantage of the ... syntax of range matching to create an inclusive range for categories of HTTP status codes. That is, 300...307 is a range that includes 300, 307, and everything in between. You also have cases with a single HTTP status code (the second case) and with two codes explicitly listed and separated by a comma (the first case), as well as a default case. These are formed in the same way as the cases you saw before. All of the case syntax options can be combined in a switch statement. The result of this switch statement is that statusMessage is set to equal "The request failed with the error: Client error, 4xx." Again, try changing the value of statusCode to see the other results. Be sure to set it back to 404 before continuing. Value binding Suppose you want to include the actual numerical status codes in your statusMessage, whether the status code is recognized or not. You can build on your previous switch statement to include this information using value binding feature. Value binding allows you to bind the matching value in a certain case to a local constant or variable. The constant or variable is available to use only within the matching case body. Listing 5.4 Using value binding ... switch statusCode { case 100, 101: statusMessage += " Informational, 1xx." statusMessage += " Informational, \(statusCode)." case 204: statusMessage += " Successful but no content, 204." case 300...307: statusMessage += " Redirection, 3xx." statusMessage += " Redirection, \(statusCode)." case 400...417: statusMessage += " Client error, 4xx." statusMessage += " Client error, \(statusCode)." case 500...505: statusMessage += " Server error, 5xx." statusMessage += " Server error, \(statusCode)." default: statusMessage = "Unknown. Please review the request and try again." case let unknownCode: statusMessage = "\(unknownCode) is not a known error code." } Here you use string interpolation to pass statusCode into the statusMessage in each case. Take a closer look at the last case. When the statusCode does not match any of the values provided in the cases above, you create a temporary constant, called unknownCode, binding it to the value of statusCode. For example, if the statusCode were set to be equal to 200, then your switch would set statusMessage to be equal to "200 is not a known error code." Because unknownCode takes on the value of any status code that does not match the earlier cases, you no longer need an explicit default case. Note that by using a constant, you fix the value of unknownCode. If you needed to do work on unknownCode, for whatever reason, you could have declared it with var instead of let. Doing so would mean, for example, that you could then modify unknownCode body. This example shows you the syntax of value binding, but it does not really add much. The standard default case can produce the same result. Replace the final case with a default case. Listing 5.5 Reverting to the default case ... switch statusCode { case 100, 101: statusMessage += " Informational, \(statusCode)." case 204: statusMessage += " Successful but no content, 204." case 300...307: statusMessage += " Redirection, \(statusCode)." case 400...417: statusMessage += " Client error, \(statusCode)." case 500...505: statusMessage += " Server error, \(statusCode)." case let unknownCode: statusMessage = "\(unknownCode) is not a known error code." default: statusMessage = "\(statusCode) is not a known error code." } In the final case in Listing 5.4, you declared a constant with a value that was bound to the status code. This meant that the final case by definition matched everything that had not already matched a case in the switch statement. The switch statement was, therefore, exhaustive. When you deleted the final case, the switch was no longer exhaustive. This means that you had to add a default case to the switch. where clauses The code above is fine, but it is not great. After all, a status code of 200 is not really an error 200 represents success! Therefore, it would be nice if your switch statement did not catch these cases. To fix this, use a where clause to make sure unknownCode is not a 2xx indicating success. where allows you to check for additional conditions that must be met for the case to match and the value to be bound. This feature creates a sort of dynamic filter within the switch. Listing 5.6 Using where to create a filter import Cocoa var statusCode: Int = 404 var statusCode: Int = 204 var statusMessage: String = "The request failed with the error:" switch statusCode { case 100, 101: statusMessage += " Informational, \(statusCode)." case 204: statusMessage += " Successful but no content, 204." case 300...307: statusMessage += " Redirection, \(statusCode)." case 400...417: statusMessage += " Client error, \(statusCode)." case 500...505: statusMessage += " Server error, \(statusCode)." case let unknownCode where (unknownCode >= 200 && unknownCode < 300) || unknownCode > 505: statusMessage = "\(unknownCode) is not a known error code." default: statusMessage = "\(statusCode) is not a known error code." statusMessage = "Unexpected error encountered." } Your case for unknownCode now specifies a range of status codes, which means that it is not exhaustive. This is not a problem because you already have a default case in the switch. Without Swift fallthrough feature, the switch statement will finish execution as soon as it finds a matching case and executes its body. When statusCode is equal to 204, the switch will match at the second case and the statusMessage will be set accordingly. So, even though 204 is within the range specified in the where clause, the switch statement never gets to that clause. Change statusCode to exercise the where clause and confirm that it works as expected. Tuples and pattern matching Now that you have your statusCode and statusMessage, it would be helpful to pair those two pieces. Although they are logically related, they are currently stored in independent variables. A tuple can be used to group the two. A tuple is a finite grouping of two or more values that are deemed by the developer to be logically related. The different values are grouped as a single, compound value. The result of this grouping is an ordered list of elements. Create your first Swift tuple that groups the statusCode and statusMessage. Listing 5.7 Creating a tuple import Cocoa var statusCode: Int = 204 var statusCode: Int = 418 var statusMessage: String = "The request failed with the error:" switch statusCode { case 100, 101: statusMessage += " Informational, \(statusCode)." case 204: statusMessage += " Successful but no content, 204." case 300...307: statusMessage += " Redirection, \(statusCode)." case 400...417: statusMessage += " Client error, \(statusCode)." case 500...505: statusMessage += " Server error, \(statusCode)." case let unknownCode where (unknownCode >= 200 && unknownCode < 300) || unknownCode > 505: statusMessage = "\(unknownCode) is not a known error code." default: statusMessage = "Unexpected error encountered." } let error = (statusCode, statusMessage) You made a tuple by grouping statusCode and statusMessage within parentheses. The result was assigned to the constant error. The elements of a tuple can be accessed by their index. You might have noticed .0 and .1 in the value of your tuple shown in the results sidebar indices. Type in the following to access each element stored inside of the tuple. Listing 5.8 Accessing the elements of a tuple ... let error = (statusCode, statusMessage) error.0 error.1 You should see 418 and "Unexpected error encountered." displayed in the results sidebar for error.0 (that is, the first element stored in the tuple) and error.1 (the second element stored in the tuple), respectively. Swift elements makes for more readable code. It is not very easy to keep track of what values are represented by error.0 and error.1. Named elements allow you to use easier-to-parse code like error.code and error.error. Give your tuple elements these more informative names. Listing 5.9 Naming the tuple elements ... let error = (statusCode, statusMessage) error.0 error.1 let error = (code: statusCode, error: statusMessage) error.code error.error Now you can access your tuple elements by using their related names: code for statusCode and error for statusMessage. Your results sidebar should have the same information displayed. You have already seen an example of pattern matching when you used ranges in the switch statement cases. This form of pattern matching is called interval matching because each case attempts to match a given interval against the comparison value. Tuples are also helpful in matching patterns. Add the following code to create and switch on a new tuple. Listing 5.10 Pattern matching in tuples ... let error = (code: statusCode, error: statusMessage) error.code error.error let firstErrorCode = 404 let secondErrorCode = 200 let errorCodes = (firstErrorCode, secondErrorCode) switch errorCodes { case (404, 404): print("No items found.") case (404, _): print("First item not found.") case (_, 404): print("Second item not found.") default: print("All items found.") } You first add a few new constants. firstErrorCode and secondErrorCode represent the HTTP status codes associated with two different web requests. errorCodes is a tuple that groups these codes. The new switch statement matches against several cases to determine what combination of 404s the requests might have yielded. The underscore (_) in the second and third cases is a wildcard that matches anything, which allows these cases to focus on a specific request error code. The first case will match only if both of the requests failed with error code 404. The second case will match only if the first request failed with 404. The third case will match only if the second request failed with 404. Finally, if the switch has not found a match, that means none of the requests failed with the status code 404. Because firstErrorCode did have the status code 404, you should see "First item not found." in the results sidebar. switch vs if/else switch statements are primarily useful for comparing a value against a number of potentially matching cases. if/ else statements, on the other hand, are better used for checking against a single condition. switches also offer a number of powerful features that allow you to match against ranges, bind values to local constants or variables, and match patterns in tuples to name just a few features covered in this chapter. Sometimes you might be tempted to use a switch statement on a value that could potentially match against any number of cases, but you really only care about one of them. For example, imagine checking an age constant of type Int looking for a specific demographic: ages 18-35. You might think writing a switch statement with a single case is your best option: Listing 5.11 Single-case switch ... let age = 25 switch age { case 18...35: print("Cool demographic") default: break } age is a constant set to be equal to 25. It is possible that age could take on any value between 0 and 100 or so, but you are only interested in a particular range. The switch checks to see whether age is in the range from 18 to 35. If it is, then age is in the desired demographic and some code is executed. Otherwise, age is not in the target demographic and the default case matches, which simply transfers the flow of execution outside of the switch with the break control transfer statement. Notice that you had to include a default case; switch statements have to be exhaustive. If this does not feel quite right to you, we agree. You do not really want to do anything here, which is why you used a break. It would be better to not have to write any code when you do not want anything to happen! Swift provides a better way. In Chapter 3 you learned about if/else statements. Swift also has an if-case statement that provides pattern matching similar to what a switch statement offers. Listing 5.12 if-case ... let age = 25 switch age { case 18...35: print("Cool demographic") default: break } if case 18...35 = age { print("Cool demographic") } This syntax is much more elegant. It simply checks to see whether age is in the given range. You did not have to write a default case that you did not care about. Instead, the syntax of the if-case allows you to focus on the single case of interest: whether age is in the range of 18 to 35. if-cases can also include more complicated pattern matching, just as with switch statements. Say, for example, you wanted to know if age was greater than or equal to 21. Listing 5.13 if-cases with multiple conditions ... let age = 25 if case 18...35 = age { print("Cool demographic") } if case 18...35 = age, age >= 21 { print("In cool demographic and of drinking age") } The new code above does the same as before, but adds something new. After the comma, it also checks to see whether age is 21 or greater. In the United States, this means that the person in question is also old enough to drink. if-cases provide an elegant substitute for switch statements with only one condition. They also enjoy all of the pattern-matching power that make switch statements so wonderful. Use an if-case when you have only one case in mind for a switch and you do not care about the default case. Because if-case statements are just if/else statements with improved pattern matching, you can also write the usual else block but doing so would mean that you are effectively writing the default case and would detract from some of the if-case allure. Bronze Challenge Review the switch statement below. What will be logged to the console? After you have decided, enter the code in a playground to see whether you were right. let point = (x: 1, y: 4) switch point { case let q1 where (point.x > 0) && (point.y > 0): print("\(q1) is in quadrant 1") c ase let q2 where (point.x < 0) && point.y > 0: print("\(q2) is in quadrant 2") case let q3 where (point.x < 0) && point.y < 0: print("\(q3) is in quadrant 3") case let q4 where (point.x > 0) && point.y < 0: print("\(q4) is in quadrant 4") case (_, 0): print("\(point) sits on the x-axis") case (0, _): print("\(point) sits on the y-axis") default: print("Case not covered.") } Silver Challenge You can add more conditions to the if-case by supplying a comma-separated list. For example, you could check to see whether the person is: a) in the cool demographic, b) of drinking age in the United States, and c) not in their thirties. Add another condition to Listing 5.13 to check whether age meets all of these conditions.