Best way to handle errors when closing async in Swift 2?

I use a lot of asynchronous network request (by the way, any network request in iOS is needed using async), and I find a way to better handle Apple dataTaskWithRequest errors that does not support throws .

I have a code like this:

 func sendRequest(someData: MyCustomClass?, completion: (response: NSData?) -> ()) { let request = NSURLRequest(URL: NSURL(string: "http://google.com")!) if someData == nil { // throw my custom error } let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in // here I want to handle Apple error } task.resume() } 

I need to parse my possible user errors and handle possible connection errors with dataTaskWithRequest . Swift 2 introduced throws , but you cannot throw it out of Apple because they do not have cast and async support.

I see only a way to add an NSError return block to my completion, but as I know, using NSError is an old-style Objective-C style. ErrorType can only be used with throws (afaik).

What is the best and most modern error handling method when using Apple Network Closure? Can't use any functions for any asynchronous network functions that I understand?

+5
source share
2 answers

There are many ways to solve this problem, but I would recommend using the completion block that the Result Enum expects. this is likely to be the fastest way.

the final listing has exactly two states, success and error, which is a big advantage for the usual two optional return values ​​(data and errors), which lead to 4 possible states.

 enum Result<T> { case Success(T) case Error(String, Int) } 

Using the result in the completion block completes the puzzle.

 let InvalidURLCode = 999 let NoDataCode = 998 func getFrom(urlString: String, completion:Result<NSData> -> Void) { // make sure the URL is valid, if not return custom error guard let url = NSURL(string: urlString) else { return completion(.Error("Invalid URL", InvalidURLCode)) } let request = NSURLRequest(URL: url) NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in // if error returned, extract message and code then pass as Result enum guard error == nil else { return completion(.Error(error!.localizedDescription, error!.code)) } // if no data is returned, return custom error guard let data = data else { return completion(.Error("No data returned", NoDataCode)) } // return success completion(.Success(data)) }.resume() } 

because the return value is an enumeration, you must disable it.

 getFrom("http://www.google.com") { result in switch result { case .Success(let data): // handle successful data response here let responseString = String(data:data, encoding: NSASCIIStringEncoding) print("got data: \(responseString)"); case .Error(let msg, let code): // handle error here print("Error [\(code)]: \(msg)") } } 

another solution would be to go through two completion blocks, one for success and one for error. sort of:

 func getFrom(urlString: String, successHandler:NSData -> Void, errorHandler:(String, Int) -> Void) 
+13
source

There is an elegant approach using the Promise library, similar to JavaScript, or the Scala Future and Promise library.

Using the Scala and promises flags, it might look like this:

Your original function

func sendRequest(someData: MyCustomClass?, completion: (response: NSData?) -> ())

can be implemented as shown below. It also shows how to create a promise, return early with a bad future and how to fulfill / reject the promise:

 func sendRequest(someData: MyCustomClass) -> Future<NSData> { guard let url = ... else { return Future.failure(MySessionError.InvalidURL) // bail out early with a completed future } let request = ... // setup request let promise = Promise<NSData>() NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in guard let error = error else { promise.reject(error) // Client error } // The following assertions should be true, unless error != nil assert(data != nil) assert(response != nil) // We expect HTTP protocol: guard let response = response! as NSHTTPURLResponse else { promise.reject(MySessionError.ProtocolError) // signal that we expected HTTP. } // Check status code: guard myValidStatusCodeArray.contains(response.statusCode) else { let message: String? = ... // convert the response data to a string, if any and if possible promise.reject(MySessionError.InvalidStatusCode(statusCode: response.statusCode, message: message ?? "")) } // Check MIME type if given: if let mimeType = response.MIMEType { guard myValidMIMETypesArray.contains(mimeType) else { promise.reject(MySessionError.MIMETypeNotAccepted(mimeType: mimeType)) } } else { // If we require a MIMEType - reject the promise. } // transform data to some other object if desired, can be done in a later, too. promise.fulfill(data!) }.resume() return promise.future! } 

You can expect a JSON response in response - if the request is successful.

Now you can use it as follows:

 sendRequest(myObject).map { data in return try NSJSONSerialization.dataWithJSONObject(data, options: []) } .map { object in // the object returned from the step above, unless it failed. // Now, "process" the object: ... // You may throw an error if something goes wrong: if failed { throw MyError.Failed } } .onFailure { error in // We reach here IFF an error occurred in any of the // previous tasks. // error is of type ErrorType. print("Error: \(error)") } 
0
source

Source: https://habr.com/ru/post/1241281/


All Articles