何时使用 Swift 使用 do-catch 块

When to use do-catch block using Swift

在从文件中读取 JSON 数据的以下场景中,我有以下代码块:

// Fetch URL
let url = Bundle.main.url(forResource: "sampleJSON", withExtension: "json")!

// Load Data
let data = try! Data(contentsOf: url)

// Deserialize JSON
let json = try! JSONSerialization.jsonObject(with: data, options: [])

这个代码块本身是否正确,或者将它包含在 do-catch 块中是否更好?我问是因为我看到了使用 URLSession 从 Web 中提取数据的场景,其中开发人员在 do-catch 块中执行 JSON 序列化。为什么在使用 URLSession 时这样做,而不是在简单地从文件中提取 JSON 数据时这样做?像这样的最佳做法是什么?

您通常应该为 throwable 的每个函数使用一个 try-catch 块。使用您当前的代码,如果您的任何一个 try 块失败(无法从 url 下载数据或者它不是有效的 json),强制尝试将失败并导致运行时异常。

let url = Bundle.main.url(forResource: "sampleJSON", withExtension: "json")!
do {
    let data = try Data(contentsOf: url)
    let json = try JSONSerialization.jsonObject(with: data, options: [])
} catch {
    //handle error
}

如果您不关心函数会抛出的错误,您可以使用 try? which returns a nil 当函数抛出错误时。这样你的代码就不会崩溃。

guard let data = try? Data(contentsOf: url) else {return}
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) else {return}

Firstly,建议我们将任何抛出(带有 try)的函数包装到一个 do-catch 块中。在我看来,我也会执行一个 do-catch 块,以防我在不知情的情况下修改 sampleJSON 文件,这会导致 JSON 的格式被打乱。

Secondly,代码中never use force unwrap at all绝对是个好习惯。

Lastly,我们应该始终捕获序列化异常并抛给调用者,最终将通知视图抛出一个错误对话框。

1 - 这个代码块本身是否正确,或者将它包含在 do-catch 块中是否更好?

答:这段代码是正确的。如果您的 sampleJSON.json 文件在您的包中,它将起作用 AND 您的 JSON 文件中的数据格式正确 AND JSONSerialization 成功解析提供的数据。

2 - 我问是因为我看到了使用 URLSession 从 Web 中提取数据的场景,其中开发人员在 do-catch 块中执行 JSON 序列化。为什么在使用 URLSession 时这样做,而不是在简单地从文件中提取 JSON 数据时这样做?像这样的最佳做法是什么?

A:在从网络上使用数据(JSON 在这种情况下)时,do-catch 语句更常见,因为 API 可能因任何原因(数据规范错误)而中断必须显示,网络应用程序本身的错误等),如果发生这种情况,我们不希望我们的应用程序崩溃。

我说 CRASH 是因为您使用了 !,它不会将错误传播到应用程序的上层,它会尝试强制操作,如果失败会使应用程序崩溃。

在这一点上你可能意识到你在使用你的包中的数据时看不到 do-catch 语句的原因是因为应用开发者自己提供了 JSON 所以我假设你确定关于文件的内容,但我仍然会使用 do-catch 语句,因为可能会出现问题并且不希望我的应用程序因这样的愚蠢事情而崩溃。

TL;DR

我建议 始终 使用 throws/rethrows 甚至 ? 的错误传播,这样你就可以测试零结果。

我已经写了一个 small article here 有一些技巧和它在 Swift 2.1 中的工作原理,在 Swift 3.1 中没有太大变化所以你可以用来研究 do-catch声明。

我会像这样重写您提供的代码:

警告:未经测试的代码

enum JSONFromFileError: Error {
    case fileNotInBundle(String)
    case deserializationError
    case getDataError(URL)
}

func json(from file: String) throws -> Any {
    // Fetch URL in Bundle
    guard let url = Bundle.main.url(forResource: file, withExtension: "json") else {
        throw JSONFromFileError.fileNotInBundle(file)
    }

    // Load Data from url
    guard let data = try? Data(contentsOf: url) else { 
        throw JSONFromFileError.getDataError(url)
    }

    // Deserialize JSON
    guard let json = try? JSONSerialization.jsonObject(with: data, options: []) else {
        throw JSONFromFileError.deserializationError
    }

    return json
}

do {
    let myJsonObject = try json(from: "sampleJSON")
    print(myJsonObject)
} catch let error {
    print(error)
}