The switch
statement in Swift lets you inspect a value and match it with a number of cases. It’s particularly effective for taking concise decisions based on one variable that can contain a number of possible values. Using the switch
statement often results in more concise code that’s easier to read.
In this tutorial, you’ll learn how to use the switch
statement in Swift. We’ll get into:
switch
statement is, and what it’s forswitch
fallthrough
switch
with interval and range casesswitch
statementwhere
in the switch
statementReady? Let’s go.
Here’s what the Swift Language Guide has to say about the switch
statement:
A switch statement considers a value and compares it against several possible matching patterns.
Let’s find out what that means, with an example. Consider the following enumeration:
enum Compass {
case north
case east
case south
case west
}
When you want to find your bearings, you could use this code:
let heading = Compass.south
if heading == .north {
print("You're heading north!")
} else if heading == .east {
print("You're heading east!")
} else if heading == .south {
print("You're heading south!")
} else if heading == .west {
print("You're heading west!")
}
The above code uses an if statement to evaluate the value of heading
, and print out the relevant line of text.
Here’s how you can do the same using a switch
statement:
switch heading {
case .north:
print("You're heading north!")
case .east:
print("You're heading east!")
case .south:
print("You're heading south!")
case .west:
print("You're heading west!")
}
The syntax of the switch
statement is simple:
switch
keyword, and then an expression, such as the constant heading
. This is the value that’s being considered by the switch
block.case
. In the above example we’re considering every possible value of the Compass
enumeration.The switch
statement is much more powerful than it seems, especially compared to the if
statement. One of its advantages is that every switch
block needs to be exhaustive.
Here, check out what happens when we forget to include .east
:
switch heading {
case .north:
print("You're heading north!")
case .south:
print("You're heading south!")
case .west:
print("You're heading west!")
}
The output is: error: switch must be exhaustive
.
Oops! That means that we’ll need to assess every value of Compass
. When we don’t, we’ll see a compile-time error. This is particularly helpful if you’re changing an enumeration later on in your app. You wouldn’t get a warning if you had used if
statements.
The advantages of the switch
statement go beyond merely checking enums for exhaustiveness (Scrabble word!). Its cases are checked at compile-time, so you can spot numerous errors before your app runs.
Even though a switch
statement needs to be exhaustive, you don’t have to explicitly specify every option.
Here, consider the following Authorization
enumeration:
enum Authorization {
case granted
case undetermined
case unauthorized
case denied
case restricted
}
When you want to give a user access to a resource based on their authorization state, you could do this:
switch state {
case .granted:
print("Access granted. You may proceed.")
default:
print("YOU SHALL NOT PASS!!")
}
The above code uses the default
case to respond to any case that’s not handled. This makes the switch
exhausive.
state
equals .granted
, the first case is executed.state
has any other value than .granted
, the default
case is executed.And you can also use compound cases, like this:
switch state {
case .granted:
print("Access granted. You may proceed.")
case .undetermined:
print("Please provide your clearance code.")
case .unauthorized, .denied, .restricted:
print("Access denied!")
}
The last case combines several cases on one line. Everyone of those .unauthorized
, .denied
and .restricted
cases will trigger the “Access denied!” response.
And last but not least, let’s take a look at implicit fallthrough. In most programming languages, switch
statement cases implicitly “fall through” to the next case. Execution starts with the first matching case, and continues down until you explicitly stop execution with break
.
In Swift, it’s exactly the opposite. Every switch
case will automatically halt. You don’t need to use break
, because Swift switch
statements don’t implicitly fall through.
This is a deliberate design decision. In most cases, you don’t want your case
to fall through. When you do, you can use fallthrough
explicitly. Like this:
var message = "Response: "
let state = Authorization.undetermined
switch state {
case .granted:
message += "Access granted. You may proceed."
case .undetermined:
message += "Please provide your clearance code. "
fallthrough
default:
message += "Access denied!"
}
print(message)
In the above example, the .undetermined
case falls through to the default
case. So, when state
is .undetermined
, both cases are executed, and two strings are added to message
, which results in this output:
Output: Response: Please provide your clearance code. Access denied!
And that’s because of the fallthrough
keyword in the .undetermined
case block.
You don’t have to explicitly break
a case
block, but if you want to prematurely exit a particular case you can use break
. Just as with for
and while
loops.
You can use the switch
statements to match intervals and ranges. Consider, for instance, how humans perceive visible light with different wavelengths:
Image by Inductiveload, NASA – CC BY-SA 3.0
We can check if a particular wavelength produces the color violet or orange. Like this:
let wavelength = 398
if wavelength >= 380 && wavelength < 450 {
print("It's violet!")
} else if wavelength >= 590 && wavelength < 620 {
print("It's orange!")
}
You can imagine that if we use an if statement to check the wavelengths of violet, blue, green, yellow, orange and red colors, the conditional becomes quite messy.
Instead, we do this with the switch
statement:
let wavelength = 620
switch wavelength {
case 380..<450:
print("Purple!")
case 450..<495:
print("Blue!")
case 495..<570:
print("Green!")
case 570..<590:
print("Yellow!")
case 590..<620:
print("Orange!")
case 620..<750:
print("Red!")
default:
print("Not in visible spectrum")
}
See what happens here? For every range of wavelengths we define an interval with the half-open range operator a..<b
. For instance, a range of 380..<450
starts at 380 nanometer and ends at 450 nm (not including 450).
In the above code, the value of wavelength
is evaluated against the cases in the switch
statement. The code determines that 620 falls within the range of 620..<750
and prints out Red!
accordingly.
This is where it gets interesting! You can use the switch
statement in Swift with tuples.
A tuple is a simple ordered list of two or more values. It’s written between parentheses, like this:
let flight = (747, "SFO")
The type of flight
is (Int, String)
. Once a tuple is defined, you can’t change its order or type. Tuples are ideal for passing several associated values in one variable.
You can access the values of a tuple with an index number, like this:
print(flight.1)
// Output: SFO
It’s more convenient to name the tuple’s values. Like this:
let flight = (airplane: 747, airport: "SFO")
print(flgith.airplane)
// Output: 747
You can use tuples together with switch
statements. Like this:
for i in 1...100
{
switch (i % 3 == 0, i % 5 == 0)
{
case (true, false):
print("Fizz")
case (false, true):
print("Buzz")
case (true, true):
print("FizzBuzz")
default:
print(i)
}
}
The above code is part of a coding challenge called FizzBuzz.
In the above code we’re creating a tuple from two values, the result of i % 3 == 0
and i % 5 == 0
. These are boolean operations, so the type of the tuple is (true, true)
.
We can now respond to different tuple values, such as (true, false)
and (true, true)
. As such, this happens in the code:
i
is divisible by 3, print “Fizz”i
is divisible by 5, print “Buzz”i
is divisible by both 3 and 5, print “FizzBuzz”i
Because we’re using switch
to evaluate a tuple, we can react to different matching values in the tuple.
Here, check this out:
let airplane = (type: "737-800", heading: "LAX")
switch airplane {
case ("737-800", _):
print("This airplane is a 737-800 model.")
case (let type, "LAX"):
print("This \(type) is headed for Los Angeles Intl. Airport")
default:
print("Unknown airplane and heading.")
}
In the above code we’re responding to 3 different cases:
_
matches any value for airplane.heading
?LAX
, irregardless of airplane type, the code prints out This … is headed for Los Angeles Intl. Airport.Consider what happens in the above scenario, where the value of airplane
is (type: "737-800", heading: "LAX")
. Which of the 3 cases is executed?
It’s the first case, because that’s the first case that matches. The type of the airplane is 373-800. Even though the LAX heading could potentially match the second case, the ("737-800", _)
is matched earlier. Don’t forget that your code executes line by line, from top to bottom!
Then, consider what happens when the value of airplane
is (type: "747", heading: "LAX")
. Now the second case is executed, because the heading
is LAX.
The let type
in case (let type, "LAX"):
is called a value binding. This temporarily binds the value from the tuple to a constant type
. Within the switch
case you can now use that constant, such as printing it out. Neat!
OK, there’s one last way of using the switch
statement that you have to get to know. It’s by using where.
Here, check out the following Swift code:
enum Response {
case error(Int, String)
case success
}
let httpResponse = Response.success
switch httpResponse {
case .error(let code, let status) where code > 399:
print("HTTP Error: \(code) \(status)")
case .error:
print("HTTP Request failed")
case .success:
print("HTTP Request was successful")
}
// Output: HTTP Request was successful
In the above code, two things happen:
Response
, that has two cases: .error
and .success
. The .error
case has two associated values, an integer and a string.httpResponse
with a switch
statement, responding to different values of the Response
enum.In the above code we’re defining two cases that are clear, namely .error
and .success
. Imagine a value of Response
comes back and we simply want to check if the response is OK or not. For that, we use .error
and .success
.
Consider what happens when you change the value of httpResponse
to:
let httpResponse = Response.error(404, "Not Found")
// Output: HTTP Error: 404 Not Found
And to:
let httpResponse = Response.error(301, "Moved Permanently")
// Output: HTTP Request failed
What happens there?
There’s one .error
case that we’re particularly interested in, namely an error code that’s greater than 399
.
Here’s the relevant code:
case .error(let code, let status) where code > 399:
print("HTTP Error: \(code) \(status)")
The official HTTP Status Codes, which are used around the internet, indicate that status codes from 400 and up are client and server errors. Differently said, when you get one of those, something went wrong.
In the above code, we’re binding the code
and status
constants to the associated values of the Response
enumeration. We’re also using the where
keyword to indicate that this case should execute for any value of code
that’s greater than 399.
You can almost literally read that as “cases of error where code is greater than 399”. It’s quite intuitive, and clearly defines that we’re specially interested in those error codes.
The if
statement isn’t the only way to make decisions in your code. The switch
statement provides numerous ways to consider a value, and to compare it with matching patterns.
And it’s especially powerful when combined with tuples, pattern matching, ranges, value bindings and where
!
Want to learn more? Check out these resources: