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];
}
}
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.