什么是错误?
在 Swift 中,错误表示程序执行期间遇到的意外情况。例如,一个网络请求失败,一个文件无法打开,或者一个算术运算尝试对 0 取余数。当这些问题出现时,我们需要抛出一个错误来告诉调用方发生了什么,并给予适当的响应。
抛出错误
在 Swift 中,我们可以使用 throw
语句来抛出一个错误。例如,以下代码抛出了一个自定义的错误:
1
2
3
4
5
6
7
8
9
10
11
12
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
let snackName = vendingMachine.vend(person: person)
guard snackName == "Chips" else {
throw VendingMachineError.invalidSelection
}
}
在上面的代码中,我们定义了一个名为 VendingMachineError
的枚举类型,它包含了三种错误类型。buyFavoriteSnack
函数尝试从自动售货机中购买人们最喜欢的零食,如果选择不正确,就会抛出一个 invalidSelection
错误。
throw
语句必须出现在可以抛出错误的函数或方法的内部。在 Swift 中,我们可以用 throws
关键字来标记一个函数或方法可能抛出错误。例如:
1
2
3
func someThrowingFunction() throws -> Int {
// ...
}
如果你的函数或方法不会抛出错误,你也可以使用 throws
关键字来明确地表明该函数或方法不会抛出任何错误。
1
2
3
func someNonThrowingFunction() -> Int {
// ...
}
处理错误
当我们调用可能抛出错误的函数或方法时,我们需要使用 do-catch
语句来捕获和处理错误。例如,以下代码尝试从文件中读取一个整数,如果读取失败,则抛出一个错误:
1
2
3
4
5
6
7
8
9
func readFile() throws -> Int {
let path = "./somefile.txt"
let data = try Data(contentsOf: URL(fileURLWithPath: path))
guard let string = String(data: data, encoding: .utf8),
let number = Int(string) else {
throw MyError.couldNotReadFile
}
return number
}
我们可以使用 do-catch
语句来捕获并处理由 readFile()
抛出的错误:
1
2
3
4
5
6
do {
let number = try readFile()
print("The number is \(number)")
} catch {
print("An error occurred: \(error)")
}
在上面的代码中,我们使用 try
关键字调用 readFile()
函数,如果函数抛出了一个错误,则通过 catch
子句来捕获并处理该错误。在 catch
子句中,我们可以使用 error
参数来访问错误对象,进而实现适当的响应。
处理多个错误
在处理错误时,我们还可以针对不同类型的错误进行不同的处理。例如,以下代码尝试从服务器上下载数据,如果下载失败,则根据不同的错误类型采取不同的操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
enum DownloadError: Error {
case invalidURL
case networkUnavailable
case serverError(statusCode: Int)
}
func downloadData(from url: String) throws -> Data {
guard let url = URL(string: url) else {
throw DownloadError.invalidURL
}
guard Reachability.isConnectedToNetwork() else {
throw DownloadError.networkUnavailable
}
let (data, response, error) = URLSession.shared.syncDataTask(with: url)
if let statusCode = (response as? HTTPURLResponse)?.statusCode,
statusCode >= 400 {
throw DownloadError.serverError(statusCode: statusCode)
}
if let error = error {
throw error
}
guard let data = data else {
throw MyError.emptyResponse
}
return data
}
do {
let data = try downloadData(from: "https://example.com/data")
processData(data)
} catch DownloadError.invalidURL {
print("Invalid URL")
} catch DownloadError.networkUnavailable {
print("Network unavailable")
} catch DownloadError.serverError(let statusCode) {
print("Server error with status code (statusCode)")
} catch {
print("An error occurred: (error)")
}
在上面的代码中,我们定义了一个名为 DownloadError
的枚举类型,它包含了三种错误类型。downloadData
函数尝试从指定的 URL 下载数据,如果下载失败,则根据不同的错误类型抛出不同的错误。在 do-catch
语句中,我们针对不同类型的错误进行不同的处理。
转换错误
有时候我们需要将一个错误转换成另一个错误类型,以便于上层代码更好地理解和处理错误。例如,以下代码尝试从文件中读取一个整数,并将其转换成一个自定义的错误:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
enum MyError: Error {
case couldNotReadFile
case invalidNumber
}
func readFile() throws -> Int {
let path = "./somefile.txt"
let data = try Data(contentsOf: URL(fileURLWithPath: path))
guard let string = String(data: data, encoding: .utf8),
let number = Int(string) else {
throw MyError.couldNotReadFile
}
return number
}
do {
let number = try readFile()
guard number >= 0 else {
throw MyError.invalidNumber
}
print("The number is \(number)")
} catch MyError.couldNotReadFile {
print("Could not read file")
} catch MyError.invalidNumber {
print("Invalid number")
} catch {
print("An error occurred: \(error)")
}
在上面的代码中,我们定义了一个名为 MyError
的枚举类型,它包含了两种错误类型。在 do-catch
语句中,我们从文件中读取一个整数,并将其转换成一个自定义的错误类型。如果读取失败,则抛出一个 couldNotReadFile
错误;如果读取的数字小于 0,则抛出一个 invalidNumber
错误。
将错误传递给调用方
当我们在函数或方法中捕获到一个错误时,我们可以选择将它传递给调用方来进一步处理。为了实现这一点,我们可以使用 throws
关键字来标记当前函数或方法可能抛出的错误类型。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum MyError: Error {
case couldNotProcessData
}
func process(data: Data) throws -> Int {
// ...
}
func handleData(data: Data) throws {
let number = try process(data: data)
print("The number is \(number)")
}
do {
let data = getDataFromServer()
try handleData(data: data)
} catch {
print("An error occurred: \(error)")
}
在上面的代码中,我们定义了一个名为 process
的函数,它尝试将输入数据处理成一个整数,如果处理失败,则抛出一个自定义的错误类型。在 handleData
函数中,我们调用了 process
函数,并将可能抛出的错误传递给了调用方。在 do-catch
语句中,我们通过调用 handleData
函数来处理数据,如果发生错误,则将其传递给调用方。
延迟处理
Swift 还提供了一种延迟处理错误的方式,即使用 defer
语句。defer
语句表示在函数或方法返回之前必须要执行的代码块,无论函数或方法是否抛出错误。例如,以下代码尝试打开一个文件,执行完之后关闭文件并释放资源:
1
2
3
4
5
6
7
8
9
swift
func openFile() throws -> File {
let file = try File(path: "./myfile.txt")
defer {
file.close()
}
// Do something with the file...
return file
}
在上面的代码中,我们定义了一个名为 openFile
的函数,它尝试打开一个文件,并在函数返回之前关闭文件。在函数中,我们使用 defer
语句来确保文件会被正确地关闭并释放资源。
自定义错误类型
除了使用 Swift 中提供的标准错误类型,我们还可以自定义错误类型以适应特定的应用程序需求。例如,以下代码定义了一个自定义的错误类型:
1
2
3
4
5
6
7
8
9
10
11
enum MyError: Error {
case invalidInput(message: String)
}
func process(input: String) throws -> String {
guard !input.isEmpty else {
throw MyError.invalidInput(message: "Input cannot be empty")
}
// Process the input...
return "Result"
}
在上面的代码中,我们定义了一个名为 MyError
的枚举类型,它包含了一种错误类型,即 invalidInput
错误。在 process
函数中,我们检查输入字符串是否为空,并在必要时抛出一个自定义的错误类型。