1

10I've followed a few posts on SOF regarding async calls using GCD and this works fine IF the remote server response is quick enough ,like when working and testing against a local test server.

SOF solutions attempted:

Waiting until two async blocks are executed before starting another block

iPhone - Grand Central Dispatch main thread

Now that I have a remote server set up its taking at least 8 seconds to return its JSON data and the GCD code is acting as if its not waiting for the async call to finish before it updates the UI and I end up with a table view that is empty.

The only way I can get this to behave correctly is by leaving in the line

[NSThread sleepForTimeInterval:10.0];

which forces the app to wait 10 seconds and allows "[self runUnirestRequest:requestUrl];" to return with data, then I get data back. This is obviously a hack and would like to get my GCD code working properly.

Is there any way to make the UI code only execute once the async call has returned with data?

Note: Data returned from runUnirestRequest is in JSON format and is deserialized and placed into an instance of "salesData".

My code related to GCD calls is as follows:

- (void)viewDidLoad
{
...unrelated code...

[self createActivityIndicator];

dispatch_queue_t jsonQueue = dispatch_queue_create("com.ppos.pbsdashboard", NULL);

//  Start block on background queue so the main thread is not frozen
//  which prevents apps UI freeze
[activityIndicator startAnimating];

dispatch_async(jsonQueue, ^{

    // Run remote RESTful request
    [self runUnirestRequest:requestUrl];

    // Force main thread to wait a bit to allow ansync dispatch
    // to get its response with data
    [NSThread sleepForTimeInterval:10.0];

    // Everything in background thread is done.
    // Call another block on main thread to do UI stuff
    dispatch_sync(dispatch_get_main_queue(), ^{

        // Back within main thread
        [activityIndicator stopAnimating];

        PBSVCDataDisplay *dataVc = [[PBSVCDataDisplay alloc] init];

        [dataVc setSalesData:salesData];

        [self performSegueWithIdentifier:@"showDataChart" sender:self];
    });
});
}

runUnirestRequest function

- (void) runUnirestRequest:(NSString*)urlToSendRequestTo
{
[requestVCMessages setTextAlignment:NSTextAlignmentCenter];
[requestVCMessages setText:@"Processing request"];

// Handle errors if any occur and display a friendly user message.
@try{

    NSDictionary* headers = @{@"p": settingsPassPhrase};

    [[UNIRest get:^(UNISimpleRequest* request) {
        [request setUrl:urlToSendRequestTo];
        [request setHeaders:headers];
    }] asJsonAsync:^(UNIHTTPJsonResponse* response, NSError *error) {

        UNIJsonNode *jsonNde = [response body];

        NSDictionary *jsonAsDictionary = jsonNde.JSONObject;

        salesData = [self deserializeJsonPacket:(NSDictionary*)jsonAsDictionary withCalenderType:[requestParameters calendType]];
    }];


}
@catch(NSException *exception){
    [requestVCMessages setTextAlignment:NSTextAlignmentLeft];
    NSString *errHeader = @"An error has occured.\n\n";
    NSString *errName = [exception name];
    NSString *errString = nil;

    // Compare the error name so we can customize the outout error message
    if([errName isEqualToString:@"NSInvalidArgumentException"]){
        errString = [errHeader stringByAppendingString:@"The reason for the error is probably because data for an invalid date has been requested."];
    }else{
        errString = [errHeader stringByAppendingString:@"General exception. Please check that your server is responding or that you have requested data for a valid date."];
    }
    salesData = nil;
    [requestVCMessages setText:errString];
}
}
Community
  • 1
  • 1
John Cogan
  • 1,034
  • 4
  • 16
  • 39
  • take a look to this other question http://stackoverflow.com/questions/11909629/waiting-until-two-async-blocks-are-executed-before-starting-another-block – tkanzakic Feb 26 '14 at 12:41
  • Thanks for your reply, I did try that and it does exactly the same. Removing the sleepForTimeInterval call also adds errors "nested push animation can result in corrupted navigation bar" and two others relates to the ""call. IS there a onComplete block for dispatch_async ? Cant seem to find anything like this. – John Cogan Feb 26 '14 at 12:49
  • 2
    What is in `runUnirestRequest`? For `onComplete` you should be using an operation queue, not a GDC queue. – Wain Feb 26 '14 at 12:55
  • 2
    could you show runUnirestRequest what actually does as you could pass your UIcode in as completion block or assign it to variable. Happy to explain more if you show how that code works. – AppHandwerker Feb 26 '14 at 12:59
  • 1
    Re: "nested push animation can result in corrupted navigation bar". I think calling `performSegueWithIdentifier` even though you have no idea whether this viewController is visible yet is probably causing this. I think you ought to be looking at putting some of this code in `viewDidAppear` rather than `viewDidLoad`. – Mike Pollard Feb 26 '14 at 13:45
  • Thanks for the replies, as requested I have added the runUnirestRequest function code. – John Cogan Feb 27 '14 at 08:00
  • @Mike, thanks. Added the dispatch code to the viewDidAppear method and this seems to have cleared up the errors but the side effect is that my apps activity indicator is not animated now. To me this is currently less of an issue though and probably down to my not fully understanding the app lifecycle and will deal with it once I have managed to fix the main issue. Thanks for your help though. – John Cogan Feb 27 '14 at 08:09

2 Answers2

0

This is not how you use asynchronous calls. What you are doing here is taking an asynchronous call and making it synchronous.

Your asynchronous code shouldn't care if the call takes 10ms, 10seconds or 10 minutes. It should work for all cases.

Maybe you should set it up something like...

- (void)viewDidLoad
{
    // other stuff...

    [self runUnirestRequest:requestUrl];

    // other stuff...
}

- (void)runUnirestRequest:(NSString*)urlToSendRequestTo
{
    // self.activityIndicator should be a property
    // accessible from anywhere in the class
    [self.activityIndicator startAnimating];

    // don't use try/catch here you already have built in error handling in the call
    NSDictionary* headers = @{@"p": settingsPassPhrase};

    [[UNIRest get:^(UNISimpleRequest* request) {
        [request setUrl:urlToSendRequestTo];
        [request setHeaders:headers];
    }] asJsonAsync:^(UNIHTTPJsonResponse* response, NSError *error) {
        if (error) {
            // handle the error here not in a try/catch block
        }

        UNIJsonNode *jsonNde = [response body];

        NSDictionary *jsonAsDictionary = jsonNde.JSONObject;

        salesData = [self deserializeJsonPacket:(NSDictionary*)jsonAsDictionary withCalenderType:[requestParameters calendType]];

        dispatch_async(dispatch_get_main_queue(), ^{
            [self.activityIndicator stopAnimating];

            PBSVCDataDisplay *dataVc = [[PBSVCDataDisplay alloc] init];

            [dataVc setSalesData:salesData];

            [self performSegueWithIdentifier:@"showDataChart" sender:self];
        });
    }];
}

You seem to be wrapping the already async call to the json request in another async block.

Just use the fact that the request is already asynchronous.

Fogmeister
  • 76,236
  • 42
  • 207
  • 306
  • Cheers Fogmeister, this works perfectly and nicely explained/pointed out where I was going wrong. Many thanks. – John Cogan Feb 27 '14 at 09:01
0

Because your runUnirestRequest is aync. And you invoke it at a dispatch_async statement. [self runUnirestRequest:requestUrl] execution will not wait until runUnirestRequest is done. You should change runUnirestRequest sync instead. This may fix your problem.

Cao Dongping
  • 969
  • 1
  • 12
  • 29