15

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.

Paymahn Moghadasian
  • 9,301
  • 13
  • 56
  • 94

2 Answers2

30

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.

cnoon
  • 16,575
  • 7
  • 58
  • 66
  • Is using [weak self] necessary when it's not being referenced? I've always understood it to be that it's not needed if the closure is not strongly referenced anywhere. – chourobin Apr 23 '15 at 19:17
  • 4
    Great question, impossible to give a concise answer to. It depends on the behavior that you want. If you want the owner of the `loginPressed` method to be held in memory regardless if nothing else is holding a reference to it, then you wouldn't want to use `[weak self]`. However, you always run the potential risk of creating a retain cycle if you don't use the weakify/strongify pattern. More info [here](http://blackpixel.com/blog/2014/03/capturing-myself.html) and [here](http://stackoverflow.com/questions/24320347/shall-we-always-use-unowned-self-inside-closure-in-swift). – cnoon Apr 24 '15 at 04:34
  • Good explanation. In Swift 3 you can substitute `if let strongSelf = self { ... }` with just `guard let strongSelf = self else { return }` – Lion Mar 25 '17 at 20:00
  • `guard` was introduced in Swift 2. – Samah Apr 04 '17 at 23:57
1

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
slik
  • 5,001
  • 6
  • 34
  • 40
  • 2
    Right, everything is treated as successful. In an error case the status code might be 500 and I presume the ```error``` will not be null. When that's the case, ```data``` is an ```AnyObject``` and I'm not sure how to access the actual ```User not found``` content. – Paymahn Moghadasian Mar 13 '15 at 04:56