Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error Handling - Async Call

I am creating a framework for web services used in my project. I have uploaded template in GitHub. https://github.com/vivinjeganathan/ErrorHandling

It has various layers. Layer 1 for validation. Layer 2 for formation of request. Layer 3 for the actual network call.

View Controller <----> Layer 1 <---> Layer 2 <---> Layer 3

Data flows between layers through closures, if error happens at any layer it needs to be gracefully passed to the ViewController.

I have referred to this link for error handling in async calls - http://appventure.me/2015/06/19/swift-try-catch-asynchronous-closures/ Created a branch in the same repo - name - ErrorHandling-Method1.

I was able to transfer error from layer 3 to layer 2(Single Level - Returning response through functions in closures - as mentioned in the link). But face difficulties in transferring back across multi layers.

Can anyone assist with the sample application provided in public GitHub?

like image 550
vivin Avatar asked Dec 19 '22 21:12

vivin


1 Answers

First of all, I don't think it's necessary to stack the layers the way you did, for example, by adding the validation functionality as a layer you are increasing coupling making that layer dependant of the layers below (parsing, networking, etc.), instead, why don't you separate validation to make it only dependant of the data?:

class ViewController: UIViewController {

   var validator = InputValidator()

   override func viewDidLoad() {
      super.viewDidLoad()

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

Now the validation functionality is not dependant of the other layers, so communication would flow like this:

View Controller <---> ParsingLayer <---> NetworkingLayer

I did rename the layers but they are not necessarily have to be like this, you can add or remove layers.

I think is going to be kind of complicated if I try to explain my approach, so I'm going to give an example using the previous layers, first the bottom layer:

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 })
         }
      }
   }
}

I have omitted some sections of code, but the idea is to do any necessary step to make the layer work (create session, etc.) and to always communicate back through the completion closure; a layer on top would look like this:

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 })
         }
      })
   } 
} 

Notice that the completion closures are not the same, since every layer adds functionality, the returned object can change, also notice that the code inside the do statement can fail in two ways, first if the network call fails and then if the data from the networking layer cannot be parsed; again the communication to the layer on top is always done through the completion closure.

Finally the ViewController can call the next layer using the closure expected by the Parsing layer in this case, and is able to handle errors originated in any layer:

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)
   }        
}

Notice that there is a do catch inside the completion closure, this is necessary since the call is made asynchronously, now that the response has gone through all the layers and have actually change to be of a more specialised type you can even validate the result without having the necessity to make a layer for the validation functionality.

Hope it helps.

like image 134
juanjo Avatar answered Dec 21 '22 09:12

juanjo