1

I have the following question.. I made an API class with all my functions and they return the result when the url request is finished.

I want to make an loader before the request is made and remove it after it's done.

My request code is this:

-(NSDictionary *)makeApiCall:(NSString *)function params:(NSDictionary *)params view:(UIView *)displayView{

__block NSDictionary *json = nil;

[self makeRequestTo:function parameters:params successCallback:^(id jsonResponse) {
    json = jsonResponse;
} errorCallback:^(NSError *error, NSString *errorMsg) {
    NSLog(@"Error: %@", errorMsg);
}];


return json;

}


- (void) makeRequestTo:(NSString *) function
        parameters:(NSDictionary *) params
   successCallback:(void (^)(id jsonResponse)) successCallback
     errorCallback:(void (^)(NSError * error, NSString *errorMsg)) errorCallback {

NSURLResponse *response = nil;
NSError *error = nil;

AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
[appDelegate setLoadingEnabled:true]
NSURL *url = [NSURL URLWithString:kBaseUrl];

AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];

httpClient.parameterEncoding = AFFormURLParameterEncoding;

NSMutableURLRequest *request = [httpClient requestWithMethod:@"POST" path:[NSString stringWithFormat:@"%@/%@",kApiBaseUrl,function] parameters:params];

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

//NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
    if(error) {
        errorCallback(error, nil);
    } else {
        id JSON = AFJSONDecode(data, &error);
        successCallback(JSON);
    }
}];

}

But.. this won't work. The loader only appear after the request is complete and it is removed within a second.

Funny thing, if i call it out from this Api class, it works.. Can anyone help me out?

Thanks

Tiago Almeida
  • 253
  • 1
  • 3
  • 9

1 Answers1

2

Updates to the user interface are only done when the program control returns to the main runloop. But the synchronous call

NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

blocks the main thread and does not return before the request has been completed. Therefore no UI updates are done, in particular your loader is not displayed.

You could use sendAsynchronousRequest instead and handle the response (and hide the loader) in the completion block, so the code would roughly look like this:

- (void) makeRequestTo:(NSString *) function
            parameters:(NSDictionary *) params
       successCallback:(void (^)(id jsonResponse)) successCallback
         errorCallback:(void (^)(NSError * error, NSString *errorMsg)) errorCallback {

    AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
    [appDelegate showLoader];

    // ... create request ...

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        [appDelegate hideLoader];

        // ... handle response ...

    }];
}

But there is another problem with your makeApiCall: method: Since makeRequestTo: works asynchronously, it returns almost immediately . The completion block with json = jsonResponse; is called later, when the request has been completed. Therefore the json variable will always been nil.

And there is no way around that. You cannot wrap an asynchronous call into a synchronous method without blocking the thread. In particular, makeApiCall: cannot return the response dictionary. You will have to change your program logic to work asynchronously.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • The result was the same.. Any more tips? :/ – Tiago Almeida Jun 02 '13 at 21:52
  • @TiagoAlmeida: Perhaps you can add the new code to your question, otherwise it is difficult to guess where the problem might be. – Martin R Jun 02 '13 at 21:55
  • @TiagoAlmeida: Your new code does not contain `showLoader` and `hideLoader` anymore, so how can the result be the same?? Has that been replaced by `[appDelegate setLoadingEnabled:true]`? In that case, where is `[appDelegate setLoadingEnabled:false]`? - But your code has another fundamental problem, see updated answer. – Martin R Jun 03 '13 at 05:25
  • Hmm, so how can i wrap this? I need to show the loader and do the request. After the request was done, remove the loader and return the json. How can i do it? – Tiago Almeida Jun 04 '13 at 21:13
  • @TiagoAlmeida: That is not possible without blocking the main thread and therefore blocking all user interface updates. - As I said, you should change your program logic to work asynchronously. For example, if you want to display the JSON response in a table view, you would assign the fetched data to the table view data source in the completion block of `makeRequestTo:...` and then call `reloadData`. – Martin R Jun 04 '13 at 21:34
  • I dont use tables, so i did it with Notifications. But now i have another problem.. After the request is done and sent the notification, the app still frozen for 5 seconds.. How can i fix that? – Tiago Almeida Jun 05 '13 at 11:07
  • @TiagoAlmeida: I have no idea without more information and seeing the relevant code. – Martin R Jun 05 '13 at 11:16
  • I updated my question here: http://stackoverflow.com/questions/16938795/nsurlconnection-5-seconds-delay – Tiago Almeida Jun 05 '13 at 11:31
  • @TiagoAlmeida: I'm glad that my other answer helped! Is this question now also solved? – Martin R Jun 05 '13 at 12:02
  • Yes it is! I can't vote up because i dont have 15 rep :( Now i understand the queue logic thanks to you Martin! – Tiago Almeida Jun 05 '13 at 12:13