Why calls swiftHTTP the response handler if there is no connection? - ios

I'm using swiftHTTP for requesting to my server and when my internet connection is slow, it goes to response part! I've set the example code below:
HTTP.GET("myURL") { response in
let myResponse = response.data // it comes here after the timeout
if response.statusCode == 200 {
//some code
} else {
do {
let jsonError = try JSON(data: myResponse) // in this line it goes to catch because there is no data in myresponse
} catch{
//alert for not having connection
}
}
Why does it call the response function if there's no response?
My server also says, that no request was sent.

It doesn't "go to response", it tries to make the HTTP request as expected and regardless of success or error it's completion handler is called.
The response object that is returned is an object that contains all of the information you need to determine what happened with the request.
So it will contain a timeout status code (HTTP 408), possibly an error message. If it did not respond at all, your app would not be able to handle these cases.
Say for example your user taps on their profile icon in the app and this sends a request to get the users profile, but it timed out. Do you want the user sat waiting, looking at a blank profile screen? It's much better to catch the error and handle it gracefully. In this case you could present a message to the user telling them that something went wrong and close the empty profile screen

Your response handler will also be called from swiftHTTP, if there's no or a very bad connection.To solve this problem, either check if there is an internet connection or check if the data is nil:
HTTP.GET("myURL") { response in
let myResponse = response.data // it comes here after the timeout
if response.statusCode == 200 || response.data == nil {
//some code
} else {
do {
let jsonError = try JSON(data: myResponse) // in this line it goes to catch because there is no data in myresponse
} catch{
//alert for not having connection
}
}
The important part here is the check if response.data == nil.

Related

Handle non JSON Response with Generic Codable API client

I have an API client that uses generic API response that conforms to Codable Protocol and uses JSONDecoder to decode the response as shown below, how do I handle having a response which doesn't return JSON ( status code 201 created)?
dataRequest.validate().responseJSON { response in
if let error = response.error {
completion(.failure(error.localizedDescription))
} else if let data = response.data {
do {
let apiResponse = try JSONDecoder().decode(T.Response.self, from: data)
completion(.success(apiResponse))
} catch {
completion(.failure(error.localizedDescription))
}
} else {
completion(.failure("Something went wrong, please try again later."))
}
}
It returns this error:
the response could not be serialized input data was nil or zero-length
In this case you can look at the statusCode property of the response (assuming that it is a HTTPURLResponse) and make your determination about whether or not there will be a body to parse. I would put it immediately after the error check.

How to show an alert to the user inside a completion block in swift 5

I have an app that makes an API call to a web server. I finally got the API call to work correctly and can now parse the JSON data the server returns, but I am stuck with trying to show an alert to the user if the request fails. For example, my server can return {"success": false, "error": "You didn't ask nicely"} Obviously that error is not real, but a representation of what can be returned. I can only check the error inside the completion block of the URLSession.shared.dataTask, but if I try to show an alert from inside that I get the error that I cannot perform any operation from a background thread on the UI.
The code is rather simple right now...
URLSession.shared.dataTask(with: self.request) { (data, response, error) in
if let error = error {
completion(.failure(error))
return
}
//continue on with processing the response...
completion(.success(fullResponse))
}.resume()
Then in my calling code I have...
connector.connect(pin) { (result) in
switch(result) {
case .success(let response):
if let response = response {
if response.success {
//do things
} else {
self.alert(title: "Error while connecting", message: response.error)
}
}
case .failure(let error):
self.alert(title: "Unable to connect", message: error)
}
}
That is causing the error that I can't do anything on the ui thread from a background thread. If that is the case, how do I let the user know that the API call failed? I have to be able to notify the user. Thank you.
You need to wrap it inside DispatchQueue.main.async as callback of URLSession.shared.dataTask occurs in a background thread
DispatchQueue.main.async {
self.alert(title: "Error while connecting", message: response.error)
}
Same also for
self.alert(title: "Unable to connect", message: error)
but it's better to wrap all code inside alert function inside the main queue to be a single place
Here's a different approach you can use, instead of calling DispatchQueue.main.async on connector.connect(pin)'s callback you could also do it before you call the completion block on your dataTask like this.
URLSession.shared.dataTask(with: self.request) { (data, response, error) in
if let error = error {
DispatchQueue.main.async {
completion(.failure(error))
}
return
}
//continue on with processing the response...
DispatchQueue.main.async {
completion(.success(fullResponse))
}
}.resume()
By doing this your code inside connector.connect(pin) won't be placed in a pyramid of doom, and everything in the completion block is running on the main thread.
connector.connect(pin) { result in
// everything in here is on the main thread now
}

Alamofire queue request if previous query is executing

When I send first request and then again I send a second request, Alamofire cancels the first request and gives error as -999 Canceled on it.
What is the way to solve so that both request get processed?
So what I want to do is, user clicks on a button one request is sent, and before that request has completed user clicks on the button again. This behaviour cancels the earlier request, rather what I want it do is, when the button is clicked and the request is sent, it should check whether the previous request is completed or still running. If it is completed then the request should process normally but if the previous request is not completed it should wait for the request to complete and once that is completed this request should be sent
self.alamoFireManager.request(request )
.responseJSON { response in
print(response.request) // original URL request
print(response.response) // URL response
print(response.data) // server data
print(response.result) // result of response serialization
guard response.result.error == nil else {
// got an error in getting the data, need to handle it
print("error calling GET on page")
print(response.result.error!)
print("Failure")
if( response.result.error?.code == -999 )
{
print("Previous Query is still Executing")
return
}

NSURLSession().dataTaskWithRequest(...) can't throw?

I have this function that takes care of an API call (makeAPICall) that I'd like to throw an error for certain API responses and when the httpResponse.statusCode != 200.
The problem is that, as far as I know, NSURLSession().dataTaskWithRequest(...) can't throw. Is this correct and if so, is there some workaround? Or should I do something totally different?
Since dataTaskWithRequest is an asynchronous operation, its error handling is facilitated with a completion handler. If it were to throw, it would be difficult to handle an error at the completion of the operation.
Therefore, you should handle the error condition within the completion handler. If you wanted to throw your own error upon completion, that would be possible but somewhat superfluous.
Instead of throwing an error, have your clients pass in a block, and run that block whenever the request fails (or, for that matter, when it completes successfully).
Actually you you can handle the error if occurs. For instance
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let error = error {
print(error)
// do whatever you want, there is an error
}
if let data = data{
print("data =\(data)")
}
if let response = response {
print("url = \(response.URL!)")
print("response = \(response)")
let httpResponse = response as! NSHTTPURLResponse
print("response code = \(httpResponse.statusCode)")
}
})
and I showed you how to get the response code as well.

Error handling in Alamofire

I have the HTTP code in an AngularJS controller:
$http.post('/api/users/authenticate', {email: $scope.email, password: $scope.password})
.success(function (data, status, headers, config) {
authService.login($scope.email);
$state.go('home');
})
.error(function (data, status, headers, config) {
$scope.errorMessages = data;
$scope.password = "";
});
In the success case, the server will respond with a JSON representation of a user. In the error case the server will respond with a simple string such as User not found which can be accessed through the data parameter.
I'm having trouble figuring out how to do something similar in Alamofire. Here's what I have right now:
#IBAction func LoginPressed(sender: AnyObject) {
let params: Dictionary<String,AnyObject> = ["email": emailField.text, "password": passwordField.text]
Alamofire.request(.POST, "http://localhost:3000/api/users/authenticate", parameters: params)
.responseJSON {(request, response, data, error) in
if error == nil {
dispatch_async(dispatch_get_main_queue(), {
let welcome = self.storyboard?.instantiateViewControllerWithIdentifier("login") as UINavigationController;
self.presentViewController(welcome, animated: true, completion: nil);
})
}
else{
dispatch_async(dispatch_get_main_queue(), {
// I want to set the error label to the simple message which I know the server will return
self.errorLabel.text = "something went wrong"
});
}
}
}
I have no idea if I'm handling the non-error case correctly either and would appreciate input on that as well.
You are are on the right track, but you are going to run into some crucial issues with your current implementation. There are some low level Alamofire things that are going to trip you up that I want to help you out with. Here's an alternative version of your code sample that will be much more effective.
#IBAction func loginPressed(sender: AnyObject) {
let params: [String: AnyObject] = ["email": emailField.text, "password": passwordField.text]
let request = Alamofire.request(.POST, "http://localhost:3000/api/users/authenticate", parameters: params)
request.validate()
request.response { [weak self] request, response, data, error in
if let strongSelf = self {
let data = data as? NSData
if data == nil {
println("Why didn't I get any data back?")
strongSelf.errorLabel.text = "something went wrong"
return
} else if let error = error {
let resultText = NSString(data: data!, encoding: NSUTF8StringEncoding)
println(resultText)
strongSelf.errorLabel.text = "something went wrong"
return
}
var serializationError: NSError?
if let json: AnyObject = NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments, error: &serializationError) {
println("JSON: \(json)")
let welcome = self.storyboard?.instantiateViewControllerWithIdentifier("login") as UINavigationController
self.presentViewController(welcome, animated: true, completion: nil)
} else {
println("Failed to serialize json: \(serializationError)")
}
}
}
}
Validation
First off, the validate function on the request will validate the following:
HTTPStatusCode - Has to be 200...299
Content-Type - This header in the response must match the Accept header in the original request
You can find more information about the validation in Alamofire in the README.
Weakify / Strongify
Make sure to weak self and strong self your closure to make sure you don't end up creating a retain cycle.
Dispatch to Main Queue
Your dispatch calls back to the main queue are not necessary. Alamofire guarantees that your completion handler in the response and responseJSON serializers is called on the main queue already. You can actually provide your own dispatch queue to run the serializers on if you wish, but neither your solution or mine are currently doing so making the dispatch calls to the main queue completely unnecessary.
Response Serializer
In your particular case, you don't actually want to use the responseJSON serializer. If you do, you won't end up getting any data back if you don't pass validation. The reason is that the response from the JSON serialization is what will be returned as the AnyObject. If serialization fails, the AnyObject will be nil and you won't be able to read out the data.
Instead, use the response serializer and try to parse the data manually with NSJSONSerialization. If that fails, then you can rely on the good ole NSString(data:encoding:) method to print out the data.
Hopefully this helps shed some light on some fairly complicated ways to get tripped up.
So Alamofire treats all requests successful. This really comes down to the API server http headers being returned.
You could use Alamofire.Request.validate()
It'll allow you to validate http headers, etc. Check out the example
https://github.com/Alamofire/Alamofire#validation
I am assuming the the error message will be in the data object.
to access the values from data you could do something like
I am not really sure about your api response looks but in this example
{
"message": "Could not authenticate"
}
let message: String? = data?.valueForKey("message") as String

Resources