3
votes

I'm using AFNetworking for POST requests. I need to wait until the completion block is done to return data, and I've run into problems.

I had a solution that was working until I switched to AFNetworking:

int i = 0;

while (!done)
{
    [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
    i++;

    NSLog(@"While Loop:%i", i);

    if (i == 149) //request timed out
        done = YES;
}

Now, the solution works sporadically. Sometimes it completes with NSLog(@"While Loop:%i", i); only logging 1 or 2, but sometimes it logs until 149 and times out. It seems like the NSRunLoop sometimes runs in a different thread, but sometimes runs on the same thread blocking my request.

Here's the code that I currently have:

- (id)postRequestWithBaseURLString:(NSString *)baseURLString function:(NSString *)function parameters:(NSDictionary *)parameters
{
    if (baseURLString)
        function = [baseURLString stringByAppendingString:function];

    __block BOOL done = NO;
    __block id returnObject = nil;

    [self.manager POST:function parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject)
     {
         NSLog(@"Success: %@", responseObject);

         done = YES;
         returnObject = responseObject;
     }

          failure:^(AFHTTPRequestOperation *operation, NSError *error)
     {
         NSLog(@"Error: %@", error);
         done = YES;
     }];

    [manager.operationQueue waitUntilAllOperationsAreFinished];

    int i = 0;

    while (!done)
    {
        [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
        i++;

        NSLog(@"While Loop:%i", i);

        if (i == 149) //request timed out
            done = YES;
    }

    return returnObject;
}

I've tried doing a dispatch_group without success. Help?

EDIT

Ok, I've got more info. My solution works when I first call the API (for example, when I first call a ViewController), but not afterwards. Perhaps, after the view is loaded, the while loop is called on the same thread as the API call, therefore blocking it?

This also seems likely, since the NSLog(@"Success: %@", responseObject); is called almost exactly after timeout happens.

1
You need to just call a different method when the completion block is done, instead of trying to return from an asynchronous block.Aaron Brager
Ok. As long as you know AFNetworking wasn't designed to be used synchronously. What if you get rid of the "timeout", and move done = YES; to after you've set returnObjectAaron Brager
Actually, I don't think you should need to get rid of the timeout - just don't set DONE to YES until the returnObject is set.Aaron Brager
@AaronBrager - Thanks so much for working on this with me. I did as you suggested, but it didn't fix the problem. However, I've updated my question. Perhaps that info might help?Cody Winton
AFNetworking completion blocks are always called on the main thread. What thread is this method called on?Aaron Brager

1 Answers

5
votes

Found an elegant solution online. I created my own completion block. Here's what I did:

+ (void)jsonRequestWithBaseURL:(NSString *)baseURL function:(NSString *)function parameters:(NSDictionary *)parameters completion:(void (^)(NSDictionary *json, BOOL success))completion
{
    if (baseURL)
        function = [baseURL stringByAppendingString:function];

    NSLog(@"%@ function:%@, parameters:%@", self.class, function, parameters);

    [AFHTTPRequestOperationManager.manager POST:function parameters:parameters success:^(AFHTTPRequestOperation *operation, id jsonObject)
     {
         //NSLog(@"Success: %@", jsonObject);

         NSDictionary *jsonDictionary = (NSDictionary *)jsonObject;

         if (completion)
             completion(jsonDictionary, YES);

     } failure:^(AFHTTPRequestOperation *operation, NSError *error)
     {
         NSLog(@"%@ AFError: %@", self.class, [error localizedDescription]);

         completion(nil, NO);
     }];
}

Then, when I need to call the method, I do this:

NSDictionary *parameters = @{@"email":@"[email protected]", @"password":@"mypassword"};

[ZAPRootViewController jsonRequestWithBaseURL:@"http://www.myserver.com/" function:@"login.php" parameters:parameters completion:^(NSDictionary *json, BOOL success)
     {
         if (success)
             {
                 //do something
             }
     }];