Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to handle errors from async closures in Swift 2?

I'm using a lot of async network request (btw any network request in iOS need to by async) and I'm finding way to better handle errors from Apple's dataTaskWithRequest which not supports throws.

I have code like that:

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's error
    }
    task.resume()
}

I need to parse my possible custom errors and handle possible connection errors from dataTaskWithRequest. Swift 2 introduced throws, but you can't throw from Apple's closure because they have no throw support and running async.

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

What's the best and most modern method to handle error when using Apple network closures? There is no way no use throws in any async network functions as I understand?

like image 956
Vasily Avatar asked Jan 22 '16 23:01

Vasily


2 Answers

there are many ways you can solve this, but i would recommend using a completion block which expects a Result Enum. this would probably be the most 'Swift' way.

the result enum has exactly two states, success and error, which a big advantage to the usual two optional return values (data and error) which lead to 4 possible states.

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

Using the result enum in a completion block finishes 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 a enum, you should switch off of 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 pass two completion blocks, one for success and one for error. something along the lines of:

func getFrom(urlString: String, successHandler:NSData -> Void, errorHandler:(String, Int) -> Void)
like image 163
Casey Avatar answered Nov 13 '22 16:11

Casey


It's very similar to Casey's answer, but with Swift 5, now we have Result (generic enumeration) implementation in standard library,

//Don't add this code to your project, this has already been implemented
//in standard library.
public enum Result<Success, Failure: Error> {
    case success(Success), failure(Failure)
}

It's very easy to use,

URLSession.shared.dataTask(with: url) { (result: Result<(response: URLResponse, data: Data), Error>) in
    switch result {
    case let .success(success):
        handleResponse(success.response, data: success.data)
    case let .error(error):
        handleError(error)
    }
}

https://developer.apple.com/documentation/swift/result

https://github.com/apple/swift-evolution/blob/master/proposals/0235-add-result.md

like image 30
Okan Kocyigit Avatar answered Nov 13 '22 18:11

Okan Kocyigit