Wait until NSURLConnection sendAsynchronousRequest is finished

ios, objective-c

Solution

It seems that you're trying to take an asynchronous process (`sendAsynchronousRequest`) , and make it behave like a synchronous process (i.e. you appear to want to wait for it). You should not do that. You should to embrace the asynchronous patterns rather than fighting them.

The `sendAsynchronousRequest` method has a completion block that specifies what you want to do when the request is done. Do not try to put the code after the block and (try to) wait for the block to complete, but rather put any of your code that is dependent upon the completion of the network request inside the completion block, or have the completion block call your code.

A common way would be to give your own methods their own completion blocks and then call those blocks in the `completionHandler` of `sendAsynchronousRequest`, something like:

- (void)performHttpRequest:(NSDictionary *)postDict completion:(void (^)(NSDictionary *dictionary, NSError *error))completion
{
    // prepare the request

    // now issue the request

    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        if (error) {
            if (completion)
                completion(data, error);
        } else {
            NSString *responseBody = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            returnDict             = [NSJSONSerialization JSONObjectWithData:data
                                                                     options: NSJSONReadingMutableContainers
                                                                      error: &error];
            if (completion)
                completion(returnDict, error);
    }];
}

Now, when you want to perform your request, you simply do:

[self performHttpRequest:someDictionary completion:^(NSDictionary *dictionary, NSError *error) {
    if (error) {
        // ok, handle the error here
    } else {
        // ok, use the `dictionary` results as you see fit here
    }
];

Note, the method that calls this `performHttpRequest` (let's imagine you called it from `loadModelFrom `) now behaves asynchronously, itself. So you might want to employ this completion-block pattern again, e.g. adding your own `completion` block parameter to `loadModelFrom`, and then invoke that block in the completion handler `loadModelFrom` passes to `performHttpRequest`.

But hopefully you get the idea: Never try to wait for a completion block, but rather just put inside that block anything you want it to do when its done. Whether you use AFNetworking (which I'd advise), or continue to use `sendAsynchronousRequest`, this is a very useful pattern with which you should become familiar.

Update:

The revised code sample (largely) works great for me. Seeing your revised question, a couple of observations:

I am not familiar with this `base64EncodedString` method. In iOS 7, there is the native `base64EncodedStringWithOptions` method (or for earlier iOS versions use `base64Encoding`). Or are you using a third party base-64 `NSData` category?

There's no point in creating `jsonString`, only to then convert it back to a `NSData`. Just use `jsonData` in your request.

The same is true with `responseBody`: Why convert to string only to convert back to `NSData`?

There's no point in having `returnDict` to be defined as `__block` outside the `sendAsynchronousRequest` block. Just define it inside that block and the `__block` qualifier is then no longer necessary.

Why create a `NSOperationQueue` for the `completionHandler` of `sendAsynchronousRequest`? Unless I'm doing something really slow that merits running on a background queue, I just use `[NSOperationQueue mainQueue]`, because you invariably want to update the app's model or UI (or both), and you want to do that sort of stuff on the main queue.

The request still runs asynchronously but the `queue` parameter just specifies which queue the completion block will run on.

By the way, in `sendAsynchronousRequest`, you aren't checking to see if the request succeeded before proceeding with `JSONObjectWithData`. If the request failed, you could theoretically be losing the `NSError` object that it returned. You really should check to make sure the request succeeded before you try to parse it.

Likewise, when you originally `dataWithJSONObject` the parameters in `postDict`, you really should check for success, and if not, report the error and quit.

I notice that you're using the `NSJSONReadingMutableContainers` option. If you really need a mutable response, I'd suggest making that explicit in your block parameters (replacing all the `NSDictionary` references with `NSMutableDictionary`). I assume you don't really need it to be mutable, so I therefore recommend removing the `NSJSONReadingMutableContainers` option.

Likewise, when creating the JSON, you don't need to use the `NSJSONWritingPrettyPrinted` option. It only makes the request unnecessary larger.

Combining all of this, that yields:

-(void)performHttpRequest:(NSDictionary *)postDict completion:(void(^)(NSDictionary *dict, NSError *error))completion {
    NSError *error;
    NSString *authValue;
    NSString *authStr;

    NSData *jsonData;
    NSData *authData;
    NSURL *aUrl;
    NSMutableURLRequest *request;

    jsonData = [NSJSONSerialization dataWithJSONObject:postDict options:0 error:&error];

    if (!jsonData) {
        if (completion)
            completion(nil, error);
        return;
    }

    aUrl        = [NSURL URLWithString:@"...."];
    request     = [NSMutableURLRequest requestWithURL:aUrl
                                          cachePolicy:NSURLRequestUseProtocolCachePolicy
                                      timeoutInterval:60.0];

    authStr     = [NSString stringWithFormat:@"%@:%@", @"xx", @"xx"];
    authData    = [authStr dataUsingEncoding:NSASCIIStringEncoding];
    if ([authData respondsToSelector:@selector(base64EncodedStringWithOptions:)])
        authValue   = [NSString stringWithFormat:@"Basic %@", [authData base64EncodedStringWithOptions:0]];
    else
        authValue   = [NSString stringWithFormat:@"Basic %@", [authData base64Encoding]]; // if only supporting iOS7+, you don't need this if-else logic and you can just use base64EncodedStringWithOptions
    [request setValue:authValue forHTTPHeaderField:@"Authorization"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-type"];
    [request setHTTPMethod:@"POST"];
    [request setHTTPBody:jsonData];

    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
     {
         if (!data) {
             if (completion)
                 completion(nil, error);
             return;
         }

         NSError *parseError = nil;
         NSDictionary *returnDict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];

         if (completion) {
             completion(returnDict, parseError);
         }
     }];
}

And if this is being called from another method that needs to handle the fact that this is happening asynchronously, then it would employ a completion block pattern, too:

- (void)authenticateUser:(NSString *)userid password:(NSString *)password completion:(void (^)(BOOL success))completion
{
    NSDictionary *dictionary = @{ ... };

    [self performHttpRequest:dictionary completion:^(NSDictionary *dict, NSError *error) {
        if (error) {
            completion(NO);
            return;
        }

        // now validate login by examining resulting dictionary

        BOOL success = ...;

        // and call this level's completion block

        completion(success);
    }];
}

Then the view controller might access that method with something like:

// maybe add UIActivityIndicatorView here

[self.userModel authenticateUser:self.userTextField.text password:self.passwordTextField.text completion:^(BOOL success) {
    // remove UIActivityIndicatorView here
    if (success) {
        // do whatever you want if everything was successful, maybe segue to another view controller
    } else {
        // show the user an alert view, letting them know that authentication failed and let them try again
    }
}];
         

Problem

I have the following problem. I have a Model, called User. When the user now logins with Facebook, my app checks if the user exists already in the database. To not freeze the UI (since I'm coming from Android) I thought to use `NSURLConnection sendAsynchronousRequest`. What worked at first was the following: My User Model had a method to do the whole task of the `AsynchronousRequest` and then when finished would set a variable to loading. Then other classes, could simply check with `while ( !user.loading )` if the Request was finished or not. The problem that came here to me, was, that now, I had to put this method in every Model. So instead of this, I created a new Class `HTTPPost`. This class now has the method that gets an `NSDictionary` passed and returns one. This works ALMOST. The problem I was now encountering is, that I couldn't really determine if the process was finished or not. So I started to create a new class called `Globals` and use global Variable `loading`. But the global variable is ALWAYS NO. So, what would be the best way to do this? Here is my code: This is where I check for the user and load it. `resultDictionary` is the `NSDictionary` where everything gets loaded in, but is always `nil` ``` [user loadModelFrom:[NSString stringWithFormat:@"WHERE facebookId='%@'", graphUser.id]]; NSLog(@"%@", user.resultDictionary); if ( user.resultDictionary == nil ) { NSLog(@"NIL"); } else { NSLog(@"NOT NIL"); } ``` The problem now, is, that, since I'm sending an AsynchronousRequest, the resultDictionary is always nil. What I did before and worked was the following. In my Model I had the HTTP Request and a variable named `loading`. Now I set loading to false until the response has been made into a `NSDictionary` ``` returnDict = [NSJSONSerialization JSONObjectWithData: [responseBody dataUsingEncoding:NSUTF8StringEncoding] options: NSJSONReadingMutableContainers error: &error]; ``` But, then I had another problem. I had to do this in all my Models again... So I created a new Class that subclasses NSObject, that has the asynchronousRequest. This is the whole request ``` -(NSDictionary *)doHttpRequest:(NSDictionary *)postDict{ loading = NO; __block NSDictionary *returnDict; NSError *error; NSString *jsonString; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:postDict options:NSJSONWritingPrettyPrinted // Pass 0 if you don't care about the readability of the generated string error:&error]; if (! jsonData) { NSLog(@"Got an error: %@", error); } else { jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; } NSURL *aUrl = [NSURL URLWithString:@"http://xx.xx-xx.xx/xx.xx"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:aUrl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSString *authStr = [NSString stringWithFormat:@"%@:%@", @"xx", @"xx"]; NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding]; NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodedString]]; [request setValue:authValue forHTTPHeaderField:@"Authorization"]; [request setValue:@"application/json" forHTTPHeaderField:@"Content-type"]; [request setHTTPMethod:@"POST"]; [request setHTTPBody:[jsonString dataUsingEncoding:NSUTF8StringEncoding]]; [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { NSString *responseBody = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; returnDict = [NSJSONSerialization JSONObjectWithData: [responseBody dataUsingEncoding:NSUTF8StringEncoding] options: NSJSONReadingMutableContainers error: &error]; }]; [queue waitUntilAllOperationsAreFinished]; loading = YES; return returnDict; } ``` As you can see I have now a variable called `loading`. It is a global variable. But somehow, the variable is always NO. What would be the best way to do this? I hope I'm understandable, I'm new to Objective-C, and English isn't my native language. UPDATE I modified the code to look like a user provided here, but still not working! ``` HTTPPost.h -(void)doHttpRequest:(NSDictionary *)postDict completion:(void(^)(NSDictionary *dict, NSError *error))completion { __block NSDictionary *returnDict; NSError *error; NSString *jsonString; NSString *authValue; NSString *authStr; NSData *jsonData; NSData *authData; NSURL *aUrl; NSMutableURLRequest *request; NSOperationQueue *queue; jsonData = [NSJSONSerialization dataWithJSONObject:postDict options:NSJSONWritingPrettyPrinted error:&error]; if (! jsonData) { NSLog(@"Got an error: %@", error); } else { jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; } aUrl = [NSURL URLWithString:@"http://xx.xx-xx.com/xx.php"]; request = [NSMutableURLRequest requestWithURL:aUrl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0]; queue = [[NSOperationQueue alloc] init]; authStr = [NSString stringWithFormat:@"%@:%@", @"xx", @"xx"]; authData = [authStr dataUsingEncoding:NSASCIIStringEncoding]; authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodedString]]; [request setValue:authValue forHTTPHeaderField:@"Authorization"]; [request setValue:@"application/json" forHTTPHeaderField:@"Content-type"]; [request setHTTPMethod:@"POST"]; [request setHTTPBody:[jsonString dataUsingEncoding:NSUTF8StringEncoding]]; [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { NSString *responseBody = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; returnDict = [NSJSONSerialization JSONObjectWithData: [responseBody dataUsingEncoding:NSUTF8StringEncoding] options: NSJSONReadingMutableContainers error: &error]; if ( completion ) { completion(returnDict, error); } }]; } //User.h [_httpPost doHttpRequest:_dbDictionary completion:^(NSDictionary *dict, NSError *error) { NSLog(@"completed") // NEVER GETS FIRED }]; ```

Original source