处理Swift中的错误

Luc*_*uke 22 error-handling ios swift

在我的应用程序中,我需要从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()
}
Run Code Online (Sandbox Code Playgroud)

Pet*_*sby 13

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

我喜欢在概念上看到Responder链如何用于处理事件.就像遍历响应者链的事件一样,错误有可能冒出App的抽象级别.根据错误,您可能希望执行与错误类型相关的许多操作.您的应用程序的不同组件可能需要了解错误,它可能是一个错误,取决于应用程序的状态不需要任何操作.

您作为开发人员最终会知道哪些错误会影响您的应用以及如何.因此,我们如何选择实施技术解决方案.

我建议使用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)")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

((errorCode: MyAppErrorCode) -> Void)
Run Code Online (Sandbox Code Playgroud)

新的下载功能

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()
}
Run Code Online (Sandbox Code Playgroud)

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

您现在可以使用组件来定义自己的错误处理解决方案,该解决方案可以轻松定制到您的应用程序,您可以使用它来将http代码和任何其他第三方错误/响应代码映射到应用程序中有意义的内容.您还可以选择让NSError冒泡是否有用.


编辑

回到我们的设计.

我们如何处理与视图控制器的交互?我们可以选择拥有现在的集中机制,或者我们可以在视图控制器中处理它并将范围保持在本地.为此,我们将逻辑从ENUM移动到视图控制器并定位视图控制器任务的非常具体的要求(在这种情况下下载),您也可以将ENUM移动到视图控制器的范围.我们实现了封装,但最轻微的是最终会在项目的其他地方重复我们的代码.无论哪种方式,您的视图控制器都必须对错误/结果代码执行某些操作

我更喜欢的方法是让视图控制器有机会处理完成处理程序中的特定行为,或者/然后将其传递给我们的ENUM以获得更一般的行为,例如发送下载已完成的通知,更新应用程序状态或只需使用"OK"的单个操作抛出AlertViewController.

我们通过向视图控制器添加方法来执行此操作,该方法可以传递MyAppErrorCodeENUM和任何相关变量(URL,Request ...)并添加任何实例变量以跟踪我们的任务,即不同的URL或尝试次数在我们放弃尝试下载之前.

以下是在视图控制器上处理下载的可能方法:

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

    }
}
Run Code Online (Sandbox Code Playgroud)