错误处理 - 异步调用

时间:2015-12-23 17:51:59

标签: ios swift asynchronous error-handling

我正在为我的项目中使用的Web服务创建一个框架。我在GitHub上传了模板。 https://github.com/vivinjeganathan/ErrorHandling

它有各种层次。第1层用于验证。第2层用于形成请求。第3层用于实际网络呼叫。

查看控制器< ---->层1< --->层2< --->第3层

数据通过闭包在层之间流动,如果在任何层发生错误,则需要将它优雅地传递给ViewController。

我在异步调用中引用此链接进行错误处理 - http://appventure.me/2015/06/19/swift-try-catch-asynchronous-closures/  在同一个repo-name中创建了一个分支 - ErrorHandling-Method1。

我能够将错误从第3层传输到第2层(单级 - 通过闭包中的函数返回响应 - 如链接中所述)。但是面对跨多层转移的困难。

任何人都可以协助公共GitHub中提供的示例应用程序吗?

4 个答案:

答案 0 :(得分:3)

我个人会使用传递NSError的通知作为图层中通知的对象,并在视图控制器中观察通知。

在图层中:

NSNotificationCenter.defaultCenter().postNotificationName("ErrorEncounteredNotification", object: error)

在视图控制器中

NSNotificationCenter.defaultCenter().addObserver(self, selector: "errorEncountered:", name: "ErrorEncounteredNotification", object: nil)

选择器方法:

func errorEncountered(notification: NSNotification!) {
    let error: NSError! = notification.object as! NSError
    NSLog("error: \(error)")
}

答案 1 :(得分:3)

首先,我认为没有必要像你那样堆叠图层,例如,通过添加验证功能作为一个层,你正在增加耦合,使得该层依赖于下面的层(解析,网络)等等,为什么不将验证分开,使其仅依赖于数据?:

class ViewController: UIViewController {

   var validator = InputValidator()

   override func viewDidLoad() {
      super.viewDidLoad()

      do {
         try validator.validateInput("INPUT")
         try Fetcher.requestDataWithParams("INPUT")
      }
      catch {
         handleError(error)
      }        
   }
}

现在验证功能不依赖于其他层,因此通信将如下所示:

查看控制器< ---> ParsingLayer< ---> NetworkingLayer

我确实重命名了图层,但它们不一定要像这样,你可以添加或删除图层。

如果我尝试解释我的方法,我认为会有点复杂,所以我将使用前面的图层给出一个例子,首先是底层:

class NetworkingLayer {
   class func requestData(params: AnyObject, completion: (getResult: () throw -> AnyObject) -> Void) -> Void {
      session.dataTaskWithURL(url) { (data, urlResponse, var error) in
         if let error = error {
            completion(getResult: { throw error })
         } else {
            completion(getResult: { return data })
         }
      }
   }
}

我省略了一些代码部分,但我们的想法是做任何必要的步骤来使图层工作(创建会话等)并始终通过完成闭包进行通信;顶部的图层如下所示:

class ParsingLayer {
   class func requestObject(params: AnyObject, completion: (getObject: () throw -> CustomObject) -> Void) -> Void {
      NetworkingLayer.requestData(params, completion: { (getResult) -> Void in
         do {
            let data = try getResult()
            let object = try self.parseData(data)
            completion(getObject: { return object })
         }
         catch {
            completion(getObject: { throw error })
         }
      })
   } 
} 

请注意,完成闭包不一样,因为每个层都添加了功能,返回的对象可以更改,还注意到do语句中的代码可能以两种方式失败,首先是网络调用失败然后,如果无法解析来自网络层的数据;再次,通过完成关闭,始终与顶层的通信完成。

最后,在这种情况下,ViewController可以使用解析层所期望的闭包调用下一层,并且能够处理源自任何层的错误:

override func viewDidLoad() {
   super.viewDidLoad()
   do {
      try validator.validateInput("INPUT")
      try ParsingLayer.requestObject("INPUT", completion: { (getObject) in
      do {
         let object = try getObject()
         try self.validator.validateOutput(object)
         print(object)
      }
      catch {
         self.handleError(error)
      }
   })
   catch {
      handleError(error)
   }        
}

请注意,在完成闭包中有一个do catch,这是必要的,因为调用是异步的,现在响应已遍历所有层,并且实际上已更改为更专业的类型甚至可以验证结果,而无需为验证功能创建图层。

希望它有所帮助。

答案 2 :(得分:2)

如果你从不抛出甚至试图抓住,为什么要声明你的方法抛出?您可以通过所有图层使用throwable声明抛出错误,甚至可以更改每个级别的throwable类型。

更新:没想到抛弃不在异步操作中工作。使用NSNotification是一条很好的路线,或者您可以查看RXSwift或类似的解决方案。我个人的建议是使用RxSwift。这可以让你摆脱目前正在进行的回调地狱。

答案 3 :(得分:2)

您在异步代码中正确识别了错误处理的问题。

使用同步函数似乎很容易 - 它只返回错误代码,或者有一个额外的错误参数,或者使用新的Swift throws语法。这是一个同步函数:

func computeSome() throws -> Some

这是异步函数的可行函数签名:

func computeSomeAsync(completion: (Some?, NSError?) -> ())

异步函数返回Void并且不抛出。如果失败,则使用错误参数集调用其完成函数。

但是,完成处理程序变得非常麻烦,尤其是在嵌套代码中。

解决方案是使用 Future

func computeSomeAsync() -> Future<Some>

此函数是异步的,不会抛出 - 并返回 Future 。那么,未来是什么?

未来代表异步函数的最终结果。当您调用异步函数时,它会立即返回,并为结果获得占位符。这称为 future ,最终将由计算该值的基础后台任务完成

当底层任务最终成功时,此future将包含函数的计算值。如果失败,它将包含错误。

根据实际实施和未来图书馆的API,您可以通过注册 continuations 来获得结果:

let future = computeSomeAsync()

future.onSuccess { value in
    print("Value: \(value)")
}


future.onFailure { error in
    print("Error: \(error)")
}

一开始可能看起来很奇怪,但你可以用期货来做很棒的事情:

fetchUser(id).flatMap { user in
    fetchProfileImage(user.profileImageUrl).flatMap { image in
        cacheImage(image)
    }
}
.onFailure { error in
    print("Something went wrong: \(error)")
}

上述语句是异步的 - 以及函数fetchUserfetchProfileImagecacheImage。包括错误处理。