ios - 在 Swift 中处理错误

标签 ios swift error-handling

在我的应用程序中,我需要从 Web 下载一个 JSON 文件。我制作了一个 ResourceService 类,它有一个 download 方法,如下所示。我在我的应用程序的“更高级别”服务中使用此服务。您可以看到在下载过程中有很多地方可能出错。可能是服务器着火,暂时无法成功响应,也可能是临时文件移动过程中出现问题等。

现在,除了稍后尝试之外,用户可能对此无能为力。然而,他/她可能想知道出了点问题,下载或“更高级别”方法的行为无法成功。

我作为开发人员对此感到困惑,因为我不明白如何处理 Swift 中的错误。我有一个 completionHandler,如果有错误,它会接受一个错误,但我不知道我应该将哪种错误传回给调用者。

想法:

1) 如果我传递从 NSFileManager API 或 NSURLSession API 获得的错误对象,我会认为我正在将 download 方法的一些实现“泄露”给调用者。调用者如何根据错误知道预期的错误类型?可能两者兼而有之。

2) 如果我应该捕获并包装那些可能在 download 方法中发生的错误,那会是什么样子?

3) 如何处理方法内的多个错误源,以及调用可能抛出/返回 NSError 对象的方法的代码是什么样的?

作为调用者,您是否应该开始拦截您返回的错误,然后编写大量代码来根据错误代码区分消息/采取的操作?我根本不明白这个错误处理的东西,也不知道当一个方法中有很多事情可能出错时它会是什么样子。

func download(destinationUrl: NSURL, completionHandler: ((error: NSError?) -> Void)) {
    let request = NSURLRequest(URL: resourceUrl!)

    let task = downloadSession.downloadTaskWithRequest(request) {
        (url: NSURL?, response: NSURLResponse?, error: NSError?) in

        if error == nil {
            do {
                try self.fileManager.moveItemAtURL(url!, toURL: destinationUrl)
            } catch let e {
                print(e)
            }
        } else {
        }
    }.resume()
}

最佳答案

首先,这是一个很好的问题。错误处理是一项特定任务,适用于一系列令人难以置信的情况,谁知道会对您的应用程序状态产生什么影响。关键问题是什么对您的用户、应用和开发者您有意义。

我喜欢在概念上将其视为 Responder chain用于处理事件。就像遍历响应者链的事件一样,错误有可能使您的应用程序的抽象级别冒泡。根据错误的不同,您可能想要做一些与错误类型相关的事情。您的应用程序的不同组件可能需要了解错误,根据应用程序的状态,它可能不需要任何操作。

作为开发者,您最终知道错误会在何处影响您的应用以及如何影响。因此,鉴于我们如何选择实现技术解决方案。

我建议使用 EnumerationsClosures至于构建我的错误处理解决方案。

这是一个 ENUM 的人为示例。如您所见,它代表了错误处理解决方案的核心。

    public enum MyAppErrorCode {

    case NotStartedCode(Int, String)
    case ResponseOkCode
    case ServiceInProgressCode(Int, String)
    case ServiceCancelledCode(Int, String,  NSError)

    func handleCode(errorCode: MyAppErrorCode) {

        switch(errorCode) {
        case NotStartedCode(let code, let message):
            print("code: \(code)")
            print("message: \(message)")
        case ResponseOkCode:
            break

        case ServiceInProgressCode(let code, let message):
            print("code: \(code)")
            print("message: \(message)")
        case ServiceCancelledCode(let code, let message, let error):
            print("code: \(code)")
            print("message: \(message)")
            print("error: \(error.localizedDescription)")
        }
    }
}

接下来我们要定义我们的 completionHandler,它将替换 ((error: NSError?) -> Void) 您在下载方法中的闭包。

((errorCode: MyAppErrorCode) -> Void)

新的下载功能

func download(destinationUrl: NSURL, completionHandler: ((errorCode: MyAppErrorCode) -> Void)) {
    let request = NSURLRequest(URL: resourceUrl!)

    let task = downloadSession.downloadTaskWithRequest(request) {
        (url: NSURL?, response: NSURLResponse?, error: NSError?) in

        if error == nil {
            do {
                try self.fileManager.moveItemAtURL(url!, toURL: destinationUrl)
                completionHandler(errorCode: MyAppErrorCode.ResponseOkCode)

            } catch let e {
                print(e)
                completionHandler(errorCode: MyAppErrorCode.MoveItemFailedCode(170, "Text you would like to display to the user..", e))

            }


        } else {
            completionHandler(errorCode: MyAppErrorCode.DownloadFailedCode(404, "Text you would like to display to the user.."))

        }
    }.resume()
}

在您传入的闭包中,您可以调用 handleCode(errorCode: MyAppErrorCode) 或您在 ENUM 上定义的任何其他函数。

您现在拥有了定义您自己的错误处理解决方案的组件,该解决方案易于针对您的应用进行定制,并且您可以使用它来将 http 代码和任何其他第三方错误/响应代码映射到您应用中有意义的内容。您还可以选择让 NSError 冒泡是否有用。


编辑

回到我们的设计。

我们如何处理与 View Controller 的交互?我们可以选择像现在这样的集中机制,或者我们可以在 View Controller 中处理它并将范围保持在本地。为此,我们会将逻辑从 ENUM 移动到 View Controller ,并针对 View Controller 任务的非常具体的要求(在本例中为下载),您也可以将 ENUM 移动到 View Controller 的范围。我们实现了封装,但最终很可能会在项目的其他地方重复我们的代码。无论哪种方式,您的 View Controller 都必须对错误/结果代码做一些事情

我更喜欢的一种方法是让 View Controller 有机会处理完成处理程序中的特定行为,或者/然后将其传递给我们的 ENUM 以获得更一般的行为,例如发送下载已完成的通知、更新应用程序状态或只是通过“确定”的单个操作抛出 AlertViewController。

我们通过向我们的 View Controller 添加方法来实现这一点,这些方法可以传递 MyAppErrorCode ENUM 和任何相关变量(URL、请求...)并添加任何实例变量来跟踪我们的任务,即不同的 URL,或者我们放弃尝试下载之前的尝试次数。

这是在 View Controller 处理下载的可能方法:

func didCompleteDownloadWithResult(resultCode: MyAppErrorCode, request: NSURLRequest, url: NSURL) {

    switch(resultCode) {
    case .ResponseOkCode:
        // Made up method as an example
        resultCode.postSuccessfulDownloadNotification(url, dictionary: ["request" : request])

    case .FailedDownloadCode(let code, let message, let error):

        if numberOfAttempts = maximumAttempts {
            // Made up method as an example
            finishedAttemptingDownload()

        } else {
             // Made up method as an example
            AttemptDownload(numberOfAttempts)
        }

    default:
        break

    }
}

关于ios - 在 Swift 中处理错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34364986/

相关文章:

ASP.NET CustomErrors 在 global.asax 或 web.config 中发生异常时不会触发

ios - 2 在同一项目中的应用

ios - 当 vc 弹出时,在选项卡栏下显示的 UITabBar 上添加 subview

ios - 从 viewController 访问或引用 UIImageView

ios - 在不使用 SerializeObject 的情况下解析 F# 中的 Firebase JSON

asp.net-mvc - FileStreamResult错误处理

c# - (错误 : System. 格式异常已被抛出。 "Input was not in correct format")

ios - 如果 UITextField 为空,则不会出现 UIAlert

iphone - 想从邮政编码中获取州名

ios - 删除标题后如何删除 UITableView 标题上的空白区域?