Before we go on, here's some tips for users of other programming languages this year:
Optional
type since version 8. However, much of the standard library only returns null
rather than Optional
, so in many cases you would need to use Optional.ofNullable
. Scala and Kotlin make more comprehensive use of Optional
std::optional
. Like Java, many built-in functions and the standard library simply return null
. You may need to handle null
in a different way, though you could still use std::optional
in the design of your own codeBecause optional values may have been added to your language's standard library later in its lifecycle, you may need to handle null references in the traditional manner. For example, you might need to use try
/catch
to handle a lot of potential errors in Java and C#.
In Swift, optionals allow variables to have either a value or nil
(no value). This is useful when:
String
to Int
)Example:
var name: String? = "John"
In the above example, name
is an optional string, meaning it may contain a String
or be nil
. Since the value is optional, Swift safely wraps the data inside an Optional
container.
Technically speaking, the value is Optional.some("John")
— users of Rust and other languages with functional programming facilities may find this familiar.
var name: String? = "John"
name = nil
Optional variables can be set to nil
, as in line 2 above. Note: this is similar to setting a variable to None
in Python — except that Swift guarantees safer handling of these kinds of values by actually wrapping it in a value called Optional.none
.
Finally, Swift may give you an optional value when casting between data types. For example, let's create an integer (Int
) and a number with a decimal point (Double
):
let age: Int? = Int("17")
let highestScore: Double? = Double("GAME OVER")
Use ?
after the type hint to define an optional type:
let age: Int? = 25 // Can be 25 or nil, equivalent to Optional.some(25)
let username: String? = nil // No value assigned, equivalent to Optional.none
Types inside types can also be optional. For example, a list of optional integers is [Int?]
. Furthermore, an optional list of optional integers is [Int?]?
.
To safely use an optional, it must be unwrapped. This means that we carefully check if the value exists or not; if it does, we can use it. If there is no value (Optional.none
), the program safely handles the absence of the value and moves on. The unwrapped value is a concrete value.
In Python, it is possible to write code that does not handle when a value is None
, causing the program to crash when you run it. Swift detects when an optional value hasn't been unwrapped and refuses to compile the code, forcing you to write safer code that is easier to test.
Use if let
to check if an optional contains a value:
let city: String? = "Te Whanganui-a-Tara"
if let unwrappedCity = city {
print("City: \(unwrappedCity)")
} else {
print("No city supplied")
}
The unwrapped value is only usable inside the brackets. This means that the scope of that unwrapped value is limited to that part of the program only.
If you were to unwrap a value and then need to cast it to a different type, that cast would need to occur inside the brackets.
let userInput: String? = readLine()
if let ageString = userInput {
if let age = Int(ageString) { // Int() returns an Int?, so we unwrap it here.
print("In ten years, you will be \(age) years old!")
}
}
print
to ask for the user's age (Enter your age:
)print()
againreadLine()
to wait for the user to type something. (This is similar to input()
in Python).if let
to unwrap the value provided by readLine()
into a new constant called ageString
if
blockreadLine
returns an optional String
in case something unusual happens with the program that causes the function to return before any text is given; this is exceedingly unlikely to happen but you still need to do safe unwrapping, just in case!
ageString
to a new integer using Int()
if let
to safely unwrap the value provided by Int()
into a new constant called age
If you know an optional contains a value, use !
when accessing the value to force unwrap it:
let language: String? = "Swift"
print(language!) // Works if language is not nil, crashes otherwise.
Warning: Force unwrapping an optional that contains nil
causes your program to crash.
if let
, just force unwrap the value returned by readLine
and Int
As you will have seen in the notes on scope, unwrapping values successive times requires staying within the brackets. This can become very unwieldy if you have multiple unwraps indented like so:
if let b = a {
if let c = Int(b) {
if let d = Bool(c) {
if let // ... etc.
}
}
}
This construct is called the pyramid of doom.
Instead of nesting these if let
statements multiple times, you can join them into a single conditional statement. Join the let
statements together using commas.
if let b = a, let c = Int(b), let d = Bool(c) {
// Here, you can use b, c, and d safely
}
A very useful construct is guard let
. It does largely the same job as if let
. However, it allows unwrapped values to have the same scope as they have. To illustrate this, let's look at an if let
with limited scope:
if let b = a {
// b can only be used here. It has limited scope.
}
// b is NOT usable here. b's scope is limited to inside the if let.
However, with guard let
, b
can be used at the same level of 'indentation' (so to speak) as the statement itself. To do this, guard let
contains an else
block that specifies what happens if the value could not be unwrapped.
guard let b = a else {
print("Unable to unwrap the specified value. Exiting...")
exit(1) // This completely closes the program with exit code 1.
}
print(b) // b is usable here and anywhere else in the program (beyond this point).
Using guard let ... else
is very good for avoiding the pyramid of doom we saw above. It also requires you to handle errors explicitly, leading to safer code that is easier to test.
guard let
's else
block must always exit the current scope. This means that:
exit(1)
, signifying that the program has exited with an error.for
or while
loop, you can use break
or continue
:
break
completely ends the loop, regardless of whether the condition has been metcontinue
stops the current iteration of the loop but continues with the next; this is useful for allowing for
loops to completely finish.Note: these also apply to .forEach
return
to prematurely exit the functionreturn
a constantnil
map
must always return a value. However, if you wish to return nil, you can use compactMap
instead (this results in a new collection with fewer items than the original — or even none at all!)
You work for an airline and need to create a simple Swift program that lets users book a seat and select a meal preference. Since users may not always enter valid data, you must safely unwrap all user input.
readLine()
to accept inputInt?
using Int()
Int?
String?
.lowercased()
(i.e. myString.lowercased()
)
Double
)
Double?
Enter your seat number: 12
You have booked seat number 12
Select your meal preference:
1. Vegetarian
2. Standard
3. Kosher
Enter the number: 2
You have selected the Standard meal.
Do you want extra legroom? (yes/no): yes
Extra legroom added.
Enter your weight (kg): 75.5
Passenger weight recorded: 75.5 kg
Enter your seat number: A12
Invalid seat number. Please enter a number.
Select your meal preference:
1. Vegetarian
2. Standard
3. Kosher
Enter the number: 5
Invalid meal choice.
Do you want extra legroom? (yes/no): maybe
Invalid choice.
Enter your weight (kg): seventy
Invalid weight entry. Please enter a number.
let
s in a single condition, guard let
, or (ideally) both