Is there a failed block for Alamofire? - ios

I'm using Alamofire with Swift kind of this way:
Alamofire.request(.GET, urlString)
.authenticate(usingCredential: credential)
.response {
(request, responseJSON, data, error) in [..and so on]
Now I wonder how I can execute some code in case the server is e.g. completely down. Something like a failed block in ObjC.
I know that I can call something like this to get an error code:
if let response = responseJSON {
var statusCode = response.statusCode
println("-->statusCode: \(statusCode)")
}
But in the case that I can't reach the server, the .response closure won't execute, so there is no error message.
How is this handled?

If you can't reach the server, you will receive a NSURLErrorDomain error via the error variable in the closure.

Related

Incorrect response from Alamofire?

I am making a request to a server using Alamofire. Here is how i am doing it:
Alamofire.request(url, method: .post, parameters: [:] ,encoding: JSONEncoding.default).responseJSON { response in
print("response=\(response)")
print("Response=:\((response.response?.statusCode)!)")
switch response.result{
case .success :
let passList = AuthenticateSuccess(nibName: "AuthenticateSuccess", bundle: nil)
self.navigationController?.pushViewController(passList, animated: true)
print("connected")
case .failure(let error):
self.showAlertTost("", msg: "Authentication Failed. Authenticate again!", Controller: self)
}
}
This is what prints:
response=SUCCESS: {
message = "Access denied.";
}
Response=:401
connected
I want to know that if 401 is error why is success block being executed? Is failure case in Alamofire handled differently?
As the documentation says:
By default, Alamofire treats any completed request to be successful, regardless of the content of the response. Calling validate() before a response handler causes an error to be generated if the response had an unacceptable status code or MIME type.
E.g.
Alamofire.request(url, method: .post, encoding: JSONEncoding.default)
.validate()
.responseJSON { response in
...
}
With validate, non 2xx responses will now be treated as errors.
response.success depicts that the server has returned the response. Whereas 401 is something that is related to the REST response which your backend system generated. Hence add the check to response code after verifying that you have received the response to provide better information to the end-user.

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.

Alamofire doesn't catch error

I'm using Alamofire to get data from my server. However, it doesn't catch the error, as the error returned is nil. I've tested with AFNetworking, and it works fine. For both operation, the status code returned is 401 Unauthorized . Is there's something with my code?
I'm using GrapeAPI for my backend. All it does is just to return the error on fail request
GrapeAPI
error!('Unauthorized', 401)
AFNetworking
manager.GET("someUrl", parameters: nil, success: { (_, object) in
}, failure: { (operation, error) in
// These are the outputs. I'm not assigning any values
// error.localizedDescription = "Request failed: unauthorized (401)"
// statusCode = 401
})
Alamofire
Alamofire.request(.GET, "url", parameters: nil)
.response { (a,b,data,error) in
// These are the outputs. I'm not assigning any values
// error = nil
// data = {"error":"Unauthorized"}
// statusCode = 401
}
I can check the failure using the statusCode. But I prefer to check the error object instead. However, since the error is nil in Alamofire, it's quite confusing to check whether the request has failed or not.
As Matt has mentioned in the comment, I need to add .validate() before calling .response(). This is by design. Final code as below:
Alamofire.request(.GET, "url", parameters: nil)
.validate()
.response { (a,b,data,error) in
// error won't be nil now
// and statusCode will be 401
}
Read this detailed explanation(thanks!) for more information.
Alamofire does not see 401 Unauthorized as an error as it is an valid return. In your comment code you are assigning a value to error not checking it for error, it should be:
Alamofire.request(.GET, "url", parameters: nil)
.response { (a,b,data,error) in
if error != nil{
println(error.localizedDescription)
} else {
if let data = data{
//You should probably use a switch statement here
if data.statusCode == 401 {
println("Unauthorized")
} else if data.statusCode == 200 {
println("Success")
}
}
}
I am not sure if i understand correctly your problem, but I hope that help!

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

How to have a completion handler/block after Alamofire Post request?

I have a method which handles a Apple Push Notification Service remote notification. When this method is executed, I want it to call my server and do a HTTP POST request using the Alamofire library. I want to execute another method that will handle the response of the POST request.
The problem for me is that I am using an existing API to fetch a profile from the server in this POST request. So I need to use this existing API and figure out when this profile fetch is specifically triggered from the remote notification.
Since Alamofire requests are done in a background queue, how would I go about doing an execution of a method after receiving the profile back from the server?
What would be a good option to solving this issue?
Thank you!
Since Alamofire requests are done in a background queue, how would I go about doing an execution of a method after receiving the profile back from the server?
Response handling is built in to Alamofire. You can do something like this (adapted from the docs):
Alamofire.request(.POST, "http://httpbin.org/get", parameters: ["foo": "bar"])
.response { (request, response, data, error) in
println(request)
println(response)
println(error)
}
Note the .response method call, which adds a completion handler to the request object; the completion handler is invoked by Alamofire when the request completes (or fails).
It wasn't clear from your question formulation what problem you were trying to solve. But you've clarified your intent in the question comments above.
As I understand the problem now, you're got some code that updates a profile on the server and handles the server's response. The code is called in two contexts, one initiated by a manual request from the user, another initiated by a push notification. In the first case, you don't want to generate an alert after you process the response from the server, but in the second case you do.
You do indeed have a closure that you can use to handle the different behavior even though the difference happens in the asynchronous part of the process. Here's a sketch (not actual working code) of how that might look:
func updateProfile(parameters: [String:String], showAlert: Bool) {
Alamofire.request(.POST, "http://myserver.com/profile", parameters: parameters)
.response { (request, response, data, error) in
if (error == nil) {
processProfileResponse(response)
if showAlert {
showProfileWasUpdatedAlert()
}
}
}
}
Note the showAlert parameter passed in to the updateProfile method. If you pass in true, it calls the showProfileWasUpdatedAlert method to show your alert after receiving the server's response. Note that this boolean value is "captured" by the closure that handles the Alamofire response because the closure was defined inside the updateProfile function.
This, IMHO, is a better approach than declaring an app global inside your AppDelegate.
Here you go
func AlamofireRequest(method: Alamofire.Method, URLString: URLStringConvertible, parameters: [String : AnyObject]?, encoding: ParameterEncoding, headers: [String : String]?) -> Alamofire.Result<String>? {
var finishFlag = 0
var AlamofireResult: Alamofire.Result<String>? = nil
Alamofire.request(method, URLString, parameters: parameters, encoding: encoding, headers: headers)
.responseString { (_, _, result) -> Void in
if result.isSuccess {
finishFlag = 1
AlamofireResult = result
}
else {
finishFlag = -1
}
}
while finishFlag == 0 {
NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture())
}
return AlamofireResult
}

Resources