首页 Swift中的错误处理
文章
取消

Swift中的错误处理

什么是错误?

在 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 函数中,我们检查输入字符串是否为空,并在必要时抛出一个自定义的错误类型。

本文由作者按照 CC BY 4.0 进行授权

Swift中的可选链

Swift中的类型转换