HTTP POST error Handling in Swift 2

24.5k views Asked by At

I'm new here, and this is my first question... I try to write an App in Swift 2 that makes an HTTP POST request but I can't figure out how to use the new error handling of Swift 2. Can anyone tell me please how to implement the "do-try-catch" error handling of Swift 2 to the code snippet below? (This code snippet uses the old error handling of swift 1.2)

func post(params : Dictionary<String, String>, url : String) {
    var request = NSMutableURLRequest(URL: NSURL(string: url)!)
    var session = NSURLSession.sharedSession()
    request.HTTPMethod = "POST"

    var err: NSError?
    request.HTTPBody = NSJSONSerialization.dataWithJSONObject(params, options: nil/*, error: &err*/)
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.addValue("application/json", forHTTPHeaderField: "Accept")

    var task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
        print("Response: \(response)")
        var strData = NSString(data: data!, encoding: NSUTF8StringEncoding)
        print("Body: \(strData)")
        var err: NSError?
        var json = NSJSONSerialization.JSONObjectWithData(data!, options: .MutableLeaves/*, error: &err*/) as? NSDictionary

        // Did the JSONObjectWithData constructor return an error? If so, log the error to the console
        if(err != nil) {
            print(err!.localizedDescription)
            let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding)
            print("Error could not parse JSON: '\(jsonStr)'")
        }
        else {
            // The JSONObjectWithData constructor didn't return an error. But, we should still
            // check and make sure that json has a value using optional binding.
            if let parseJSON = json {
                // Okay, the parsedJSON is here, let's get the value for 'success' out of it
                var success = parseJSON["success"] as? Int
                print("Succes: \(success)")
            }
            else {
                // Woa, okay the json object was nil, something went worng. Maybe the server isn't running?
                let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding)
                print("Error could not parse JSON: \(jsonStr)")
            }
        }
    })

    task!.resume()
}
2

There are 2 answers

0
Rob On BEST ANSWER

You presumably want to wrap your NSJSONSerialization calls in do/try/catch logic as shown below.

In Swift 3:

var request = URLRequest(url: URL(string: urlString)!)

let session = URLSession.shared
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")

request.httpBody = try! JSONSerialization.data(withJSONObject: parameters)

// or if you think the conversion might actually fail (which is unlikely if you built `parameters` yourself)
//
// do {
//    request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
// } catch {
//    print(error)
// }

let task = session.dataTask(with: request) { data, response, error in
    guard let data = data, error == nil else {
        print("error: \(error)")
        return
    }

    // this, on the other hand, can quite easily fail if there's a server error, so you definitely
    // want to wrap this in `do`-`try`-`catch`:

    do {
        if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
            let success = json["success"] as? Int                                  // Okay, the `json` is here, let's get the value for 'success' out of it
            print("Success: \(success)")
        } else {
            let jsonStr = String(data: data, encoding: .utf8)    // No error thrown, but not dictionary
            print("Error could not parse JSON: \(jsonStr)")
        }
    } catch let parseError {
        print(parseError)                                                          // Log the error thrown by `JSONObjectWithData`
        let jsonStr = String(data: data, encoding: .utf8)
        print("Error could not parse JSON: '\(jsonStr)'")
    }
}

task.resume()

Or, in Swift 2

let request = NSMutableURLRequest(URL: NSURL(string: urlString)!)

let session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")

request.HTTPBody = try! NSJSONSerialization.dataWithJSONObject(parameters, options: [])

// or if you think the conversion might actually fail (which is unlikely if you built `parameters` yourself)
//
// do {
//    request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(params, options: [])
// } catch {
//    print(error)
// }

let task = session.dataTaskWithRequest(request) { data, response, error in
    guard let data = data where error == nil else {
        print("error: \(error)")
        return
    }

    // this, on the other hand, can quite easily fail if there's a server error, so you definitely
    // want to wrap this in `do`-`try`-`catch`:

    do {
        if let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSDictionary {
            let success = json["success"] as? Int                                  // Okay, the `json` is here, let's get the value for 'success' out of it
            print("Success: \(success)")
        } else {
            let jsonStr = String(data: data, encoding: NSUTF8StringEncoding)    // No error thrown, but not NSDictionary
            print("Error could not parse JSON: \(jsonStr)")
        }
    } catch let parseError {
        print(parseError)                                                          // Log the error thrown by `JSONObjectWithData`
        let jsonStr = String(data: data, encoding: NSUTF8StringEncoding)
        print("Error could not parse JSON: '\(jsonStr)'")
    }
}

task.resume()

I'd also suggest being a little more careful about forced unwrapping of data, because you want to detect/handle the errors, not crash. For example, above I use a guard statement to unwrap it.

0
Qbyte On

In general if a function throws you have to write it inside a do catch block or just mark the outer scope function (in this case post) as throws:

func post(params : Dictionary<String, String>, url : String) {
    let request = NSMutableURLRequest(URL: NSURL(string: url)!)
    let session = NSURLSession.sharedSession()
    request.HTTPMethod = "POST"

    do {
        request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(params, options: .PrettyPrinted)
    } catch {
        //handle error. Probably return or mark function as throws
        print(error)
        return
    }
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.addValue("application/json", forHTTPHeaderField: "Accept")

    let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
        // handle error
        guard error == nil else { return }

        print("Response: \(response)")
        let strData = NSString(data: data!, encoding: NSUTF8StringEncoding)
        print("Body: \(strData)")

        let json: NSDictionary?
        do {
            json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableLeaves) as? NSDictionary
        } catch let dataError {
            // Did the JSONObjectWithData constructor return an error? If so, log the error to the console
            print(dataError)
            let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding)
            print("Error could not parse JSON: '\(jsonStr)'")
            // return or throw?
            return
        }


        // The JSONObjectWithData constructor didn't return an error. But, we should still
        // check and make sure that json has a value using optional binding.
        if let parseJSON = json {
            // Okay, the parsedJSON is here, let's get the value for 'success' out of it
            let success = parseJSON["success"] as? Int
            print("Succes: \(success)")
        }
        else {
            // Woa, okay the json object was nil, something went worng. Maybe the server isn't running?
            let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding)
            print("Error could not parse JSON: \(jsonStr)")
        }

    })

    task!.resume()
}

If you don't want to handle these errors right in your post function you can just declare it as throws than you don't have to use do catch at all