Optionals are the Swift feature that surprises people most, and the one that makes Swift apps crash less often. Many languages have a value called null or nil that sneaks into your code and crashes it later. Swift makes the possibility of *no value* part of the type, so the compiler makes you deal with it before the app runs. This lesson also covers errors, the other thing that can go wrong in real apps.
What an optional is
An optional is a box that holds either a value or nothing. You write it by adding a question mark to a type. Int? means *an Int, or nothing*. The nothing is written nil. A plain Int can never be nil.
var age: Int? = nil
print(age) // nil
age = 30
print(age) // Optional(30)
let text = "42"
let number = Int(text) // Int? because the text might not be a number
print(number) // Optional(42)
print(Int("abc")) // nil
Int(text) is a good example. Not every piece of text is a number, so the result is an optional. You cannot use an Int? where an Int is needed until you open the box, and Swift gives several safe ways to do that.
Unwrapping safely
let input: String? = "Asha"
// 1. if let: use the value only when it exists
if let name = input {
print("Hello, \(name)")
} else {
print("No name")
}
// 2. guard let: leave early when it does not exist
func greet(_ input: String?) {
guard let name = input else {
print("No name")
return
}
print("Hello, \(name)") // name is usable for the rest of the function
}
// 3. ?? gives a default
let shown = input ?? "Guest"
// 4. optional chaining: stop quietly at the first nil
let length = input?.count // Int?
if letis best when you only want to do something extra if a value is there.guard letis best at the top of a function. It keeps the main path of the code flat and easy to read, and theelsepart must leave withreturn,throwor similar.??is best when there is a sensible default.?.is best when you want to read through a chain, and a missing link should simply givenil.
The danger of the exclamation mark
Adding ! after an optional is called force unwrapping. It says *I promise this is not nil*. If you are wrong, the app crashes at once. People use it in examples to keep them short, and then in real code to get past an error message. Avoid it. A crash is the worst experience for a user, and it is also very easy to avoid. The few places where ! is acceptable, such as a value that your own set-up has already filled in, should be rare and you should be able to explain each one.
let value = Int("abc")
// print(value!) // crash: found nil while unwrapping
print(value ?? 0) // safe
Errors: throw, try and catch
Not every problem is a missing value. A file may not open. A web call may fail. A user may type an amount below zero. Swift uses errors for these. An error is a value of any type that conforms to the Error protocol, usually an enum. A function that can fail is marked throws, and the caller must write try before it.
enum ExpenseError: Error {
case emptyTitle
case invalidAmount(Double)
}
func makeExpense(title: String, amount: Double) throws -> Expense {
guard !title.isEmpty else { throw ExpenseError.emptyTitle }
guard amount > 0 else { throw ExpenseError.invalidAmount(amount) }
return Expense(title: title, amount: amount)
}
do {
let e = try makeExpense(title: "Tea", amount: -3)
print(e.title)
} catch ExpenseError.emptyTitle {
print("Please enter a title")
} catch ExpenseError.invalidAmount(let value) {
print("\(value) is not a valid amount")
} catch {
print("Something else went wrong: \(error)")
}
A do block runs the risky code. If anything inside throws, Swift jumps to the first matching catch. The last bare catch handles everything else, and inside it a value called error is available. You also meet two shortcuts: try? gives an optional (nil if it failed) and try! crashes on failure, so treat it like the exclamation mark.
Errors or optionals?
- When *nothing there* is a normal answer, such as looking up a key, use an optional.
- When something *went wrong* and the caller should know why, throw an error.
- When you build a screen, turn errors into something a person can read. A friendly message and a way to try again beat a technical text.
Your task
- Write
parseAmount(_ text: String) -> Double?usingDouble(text). Test it with"12.5"and"abc". - Write a function with
guard letthat prints a greeting only when a name is given. - Add a
ExpenseError.tooLargecase and makemakeExpensethrow it when the amount is over 10,000. - Catch each error in a
doblock and print a clear message.
How to check it worked
parseAmount("abc")givesniland does not crash.- Each bad input to
makeExpenseprints its own message. - You can name three safe ways to unwrap an optional and say why
!is risky.