Skip to content

Commit 0252c46

Browse files
committed
Examples 25-30
1 parent 67240c4 commit 0252c46

File tree

10 files changed

+377
-322
lines changed

10 files changed

+377
-322
lines changed

contents/[25]error-types/index.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,35 @@ description: ""
44
order: 25
55
---
66

7+
In Swift, we often use enums and sometimes structs to create meaningful error representations. An enum must conform to the `Error` protocol to act as an error type:
8+
9+
```swift
10+
enum NetworkError: Error {
11+
case noConnection
12+
case timeout
13+
case invalidResponse
14+
}
15+
```
16+
17+
To create a struct for error representation, just make the struct conform to the `Error` protocol and define the properties that will carry the error details:
18+
19+
```swift
20+
struct CustomError: Error {
21+
let message: String
22+
let code: Int
23+
}
24+
```
25+
26+
By conforming a struct or enum to the `Error` protocol, you enable these types to be thrown as errors in your code. Using the `throw` keyword, you signal that an error has occurred and that it must be handled appropriately:
27+
28+
```swift
29+
throw NetworkError.timeout
30+
31+
throw CustomError(
32+
message: "Not found",
33+
code: 404
34+
)
35+
```
36+
37+
In the following example, you will learn how to properly catch and respond to these thrown errors using Swift's do-catch system.
738

contents/[26]do-catch/index.md

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,46 +4,74 @@ description: ""
44
order: 26
55
---
66

7-
8-
foo
7+
Effective error handling begins by defining an enum or struct to represent all possible error cases your code might encounter:
98

109
```swift
11-
let x = 5
12-
let y = 5
13-
var z = 0
14-
15-
z = x + y // addition
16-
print(z) // => 10
17-
18-
z = x - y // subtraction
19-
print(z) // => 0
20-
21-
z = x * y // multiplication
22-
print(z) // => 25
23-
24-
z = x / y // division
25-
print(z) // => 1
26-
27-
28-
var x = 10
29-
30-
x += 2 // addition
31-
print(x) // => 12
32-
33-
x -= 2 // subtraction
34-
print(x) // => 10
35-
36-
x *= 2 // multiplication
37-
print(x) // => 20
38-
39-
x /= 2 // division
40-
print(x) // => 10
10+
enum InputError: Error {
11+
case invalidNumber
12+
}
13+
14+
print("Please enter a number:")
15+
let rawInput = readLine() ?? ""
16+
17+
if let numberInput = Int(rawInput) {
18+
print(numberInput)
19+
}
20+
else {
21+
throw InputError.invalidNumber
22+
}
4123
```
4224

43-
bar
25+
Because the thrown error is not caught or handled, Swift does not know how to proceed and the program immediately crashes with a runtime error. To avoid that crash, we need to wrap the throwing code inside a do-catch block, where we can decide what to do when that specific error happens:
4426

45-
```sh
46-
swift main.swift
27+
```swift
28+
enum InputError: Error {
29+
case invalidNumber
30+
}
31+
32+
enum MathError: Error {
33+
case divisionByZero
34+
}
35+
36+
do {
37+
print("Please enter a number:")
38+
let rawA = readLine() ?? ""
39+
40+
if let a = Int(rawA) {
41+
42+
print("Please enter a second number:")
43+
let rawB = readLine() ?? ""
44+
45+
if let b = Int(rawB) {
46+
47+
if b == 0 {
48+
throw MathError.divisionByZero
49+
}
50+
else {
51+
print(a/b)
52+
}
53+
}
54+
else {
55+
throw InputError.invalidNumber
56+
}
57+
}
58+
else {
59+
throw InputError.invalidNumber
60+
}
61+
}
62+
catch is InputError {
63+
print("Input error.")
64+
}
65+
catch let error as MathError {
66+
switch error {
67+
case .divisionByZero:
68+
print("Division by zero is not possible.")
69+
}
70+
}
71+
catch {
72+
print(error)
73+
}
4774
```
4875

49-
baz
76+
In this example, both `InputError` and `MathError` are explicitly handled using separate catch blocks. The final catch block serves as a fallback, capturing any errors that do not match the previous cases.
77+

contents/[27]throwing-functions/index.md

Lines changed: 139 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,46 +4,160 @@ description: ""
44
order: 27
55
---
66

7+
In Swift, functions that can throw errors are explicitly marked with the throws keyword. This is the language's way of making error-prone operations visible and intentional.
78

8-
foo
9+
```swift
10+
enum PrinterError: Error {
11+
case outOfPaper
12+
}
13+
14+
func printDocument() throws {
15+
throw PrinterError.outOfPaper
16+
}
17+
```
18+
19+
You can be explicit about what kind of error a function throws by writing the type in parentheses:
20+
21+
```swift
22+
func printDocument() throws(PrinterError) {
23+
throw PrinterError.outOfPaper
24+
}
25+
```
26+
27+
Calling a throwing function is different from calling a regular function. You need to use the `try` keyword to tell the Swift compiler: "I know this might fail, and I'll handle it." You always have to use `try` whether the function throws any error or a specific error type:
28+
29+
```swift
30+
do {
31+
try printDocument()
32+
print("Printed successfully.")
33+
}
34+
catch {
35+
print("Printing failed: \(error)")
36+
}
37+
```
38+
39+
Let's go a bit deeper. First, we define a custom error:
40+
41+
```swift
42+
struct InputError: Error {
43+
let message: String
44+
}
45+
```
46+
47+
Then here's a function that can fail by throwing `InputError`:
948

1049
```swift
11-
let x = 5
12-
let y = 5
13-
var z = 0
50+
func readInt(
51+
message: String,
52+
errorMessage: String
53+
) throws(InputError) -> Int {
54+
print(message)
55+
let rawInput = readLine() ?? ""
56+
57+
if let number = Int(rawInput) {
58+
return number
59+
}
60+
else {
61+
throw InputError(message: errorMessage)
62+
}
63+
}
64+
```
65+
66+
In this example, the `readInt` is a typed throwing function, it can only throw `InputError`. If the user input can't be converted to an Int, we throw an `InputError` with a custom message. If the input is fine, we return the number as usual.
1467

15-
z = x + y // addition
16-
print(z) // => 10
68+
In the following snippet, we introduce a second error type:
69+
70+
```swift
71+
enum MathError: Error {
72+
case divisionByZero
73+
}
74+
75+
func main() throws {
76+
let a = try readInt(
77+
message: "Please enter A as a number:",
78+
errorMessage: "A is invalid."
79+
)
80+
let b = try readInt(
81+
message: "Please enter B as a number:",
82+
errorMessage: "B is invalid."
83+
)
84+
if b == 0 {
85+
throw MathError.divisionByZero
86+
}
87+
else {
88+
print(a/b)
89+
}
90+
}
91+
```
1792

18-
z = x - y // subtraction
19-
print(z) // => 0
93+
The `main` function can throw any error because it doesn't specify a type. We call `try` before both calls to `readInt`, because each might throw an `InputError`. We manually check for division by zero and throw a `MathError` if needed.
2094

21-
z = x * y // multiplication
22-
print(z) // => 25
95+
We can handle various error types, using catch:
2396

24-
z = x / y // division
25-
print(z) // => 1
97+
```swift
98+
do {
99+
try main()
100+
}
101+
catch let error as InputError {
102+
print(error.message)
103+
}
104+
catch let error as MathError {
105+
switch error {
106+
case .divisionByZero:
107+
print("Division by zero is not possible.")
108+
}
109+
}
110+
catch {
111+
print(error)
112+
}
113+
```
26114

115+
The `try?` keyword "converts" the throwing function into a non-throwing one. It returns an optional value instead of the original return type. If something goes wrong, `try?` returns `nil` instead of throwing an error and it returns the actual value if the function succeeds-but either way, it returns an optional:
27116

28-
var x = 10
117+
```swift
118+
func example() throws -> Int {
119+
return 10
120+
}
29121

30-
x += 2 // addition
31-
print(x) // => 12
122+
let number: Int? = try? example()
123+
print(number ?? -1)
124+
```
32125

33-
x -= 2 // subtraction
34-
print(x) // => 10
126+
Just like `try?`, the `try!` keyword lets you call a throwing function without using a do-catch block. But there's a big difference: if an error happens, that'll cause a fatal error and your app will crash:
35127

36-
x *= 2 // multiplication
37-
print(x) // => 20
128+
```swift
129+
func example() throws -> Int {
130+
return 10
131+
}
38132

39-
x /= 2 // division
40-
print(x) // => 10
133+
let number: Int = try! example()
134+
print(number)
41135
```
42136

43-
bar
137+
The `rethrows` keyword is used in function definitions. It means the function itself doesn't throw errors, but it can pass along errors from a closure parameter that throws:
138+
139+
```swift
140+
enum CustomError: Error {
141+
case ouch
142+
}
143+
144+
func execute(
145+
closure: () throws -> Void
146+
) rethrows {
147+
try closure()
148+
}
149+
150+
let block: () -> Void = {
151+
print("This works without try!")
152+
}
153+
154+
execute(closure: block)
155+
156+
let throwingBlock: () throws -> Void = {
157+
throw CustomError.ouch
158+
}
44159

45-
```sh
46-
swift main.swift
160+
try execute(closure: throwingBlock)
47161
```
48162

49-
baz
163+
The `execute` function accepts a closure that might throw an error. But `execute` itself only rethrows an error if the closure it receives actually throws one. If the closure doesn't throw, `execute` behaves like a normal function. If the closure does throw, `execute` requires you to handle that possibility with try.

0 commit comments

Comments
 (0)