You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: contents/[25]error-types/index.md
+31Lines changed: 31 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,4 +4,35 @@ description: ""
4
4
order: 25
5
5
---
6
6
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
+
enumNetworkError: Error{
11
+
casenoConnection
12
+
casetimeout
13
+
caseinvalidResponse
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
+
structCustomError: 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
+
throwCustomError(
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.
Copy file name to clipboardExpand all lines: contents/[26]do-catch/index.md
+64-36Lines changed: 64 additions & 36 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,46 +4,74 @@ description: ""
4
4
order: 26
5
5
---
6
6
7
-
8
-
foo
7
+
Effective error handling begins by defining an enum or struct to represent all possible error cases your code might encounter:
9
8
10
9
```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
+
enumInputError: Error{
11
+
caseinvalidNumber
12
+
}
13
+
14
+
print("Please enter a number:")
15
+
let rawInput =readLine() ??""
16
+
17
+
iflet numberInput =Int(rawInput) {
18
+
print(numberInput)
19
+
}
20
+
else {
21
+
throw InputError.invalidNumber
22
+
}
41
23
```
42
24
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:
44
26
45
-
```sh
46
-
swift main.swift
27
+
```swift
28
+
enumInputError: Error{
29
+
caseinvalidNumber
30
+
}
31
+
32
+
enumMathError: Error{
33
+
casedivisionByZero
34
+
}
35
+
36
+
do {
37
+
print("Please enter a number:")
38
+
let rawA =readLine() ??""
39
+
40
+
iflet a =Int(rawA) {
41
+
42
+
print("Please enter a second number:")
43
+
let rawB =readLine() ??""
44
+
45
+
iflet 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
+
catchisInputError {
63
+
print("Input error.")
64
+
}
65
+
catchlet error asMathError {
66
+
switch error {
67
+
case .divisionByZero:
68
+
print("Division by zero is not possible.")
69
+
}
70
+
}
71
+
catch {
72
+
print(error)
73
+
}
47
74
```
48
75
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.
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.
7
8
8
-
foo
9
+
```swift
10
+
enumPrinterError: Error{
11
+
caseoutOfPaper
12
+
}
13
+
14
+
funcprintDocument() 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
+
funcprintDocument() 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
+
tryprintDocument()
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
+
structInputError: Error{
43
+
let message: String
44
+
}
45
+
```
46
+
47
+
Then here's a function that can fail by throwing `InputError`:
9
48
10
49
```swift
11
-
let x =5
12
-
let y =5
13
-
var z =0
50
+
funcreadInt(
51
+
message: String,
52
+
errorMessage: String
53
+
) throws(InputError) ->Int {
54
+
print(message)
55
+
let rawInput =readLine() ??""
56
+
57
+
iflet number =Int(rawInput) {
58
+
return number
59
+
}
60
+
else {
61
+
throwInputError(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.
14
67
15
-
z = x + y // addition
16
-
print(z) // => 10
68
+
In the following snippet, we introduce a second error type:
69
+
70
+
```swift
71
+
enumMathError: Error{
72
+
casedivisionByZero
73
+
}
74
+
75
+
funcmain() throws {
76
+
let a =tryreadInt(
77
+
message: "Please enter A as a number:",
78
+
errorMessage: "A is invalid."
79
+
)
80
+
let b =tryreadInt(
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
+
```
17
92
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.
20
94
21
-
z = x * y // multiplication
22
-
print(z) // => 25
95
+
We can handle various error types, using catch:
23
96
24
-
z = x / y // division
25
-
print(z) // => 1
97
+
```swift
98
+
do {
99
+
trymain()
100
+
}
101
+
catchlet error asInputError {
102
+
print(error.message)
103
+
}
104
+
catchlet error asMathError {
105
+
switch error {
106
+
case .divisionByZero:
107
+
print("Division by zero is not possible.")
108
+
}
109
+
}
110
+
catch {
111
+
print(error)
112
+
}
113
+
```
26
114
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:
27
116
28
-
var x =10
117
+
```swift
118
+
funcexample() throws->Int {
119
+
return10
120
+
}
29
121
30
-
x +=2// addition
31
-
print(x) // => 12
122
+
let number: Int?=try?example()
123
+
print(number ??-1)
124
+
```
32
125
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:
35
127
36
-
x *=2// multiplication
37
-
print(x) // => 20
128
+
```swift
129
+
funcexample() throws->Int {
130
+
return10
131
+
}
38
132
39
-
x /=2// division
40
-
print(x) // => 10
133
+
let number: Int=try!example()
134
+
print(number)
41
135
```
42
136
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
+
enumCustomError: Error{
141
+
caseouch
142
+
}
143
+
144
+
funcexecute(
145
+
closure: () throws->Void
146
+
) rethrows {
147
+
tryclosure()
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
+
}
44
159
45
-
```sh
46
-
swift main.swift
160
+
tryexecute(closure: throwingBlock)
47
161
```
48
162
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