NSURLConnection sendAsynchronousRequest completionHandler logs appearing before updating the UI - ios

I have this code to send a json string to a server
[NSURLConnection
sendAsynchronousRequest:req
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *error)
{
if ([data length] >0 && error == nil)
{
NSLog(#"Done");
}
else if ([data length] == 0 && error == nil)
{
NSLog(#"Nothing was downloaded.");
self.resultLabel.text=#"Done!";
self.view.userInteractionEnabled = TRUE;
}
else if (error != nil){
NSLog(#"Error = %#", error);
}
}];
The asynchronous request finishes fine and the logs show up almost immediately after it is finished. However, this code:
self.resultLabel.text=#"Done!";
self.view.userInteractionEnabled = TRUE;
Takes a good 10 seconds to show up in the UI. Anyone know why this would happen?

You must perform all UI changes in main thread:
....
if ([data length] == 0 && error == nil) {
dispatch_async(dispatch_get_main_queue(), ^{
self.resultLabel.text=#"Done!";
self.view.userInteractionEnabled = TRUE;
});
}
....

Related

Objective-C: Wait to execute 'While' loop until NSURLConnection request is complete

Basically I want a way to issue a NSURLRequest multiple times in a loop until a certain condition has been met. I am using a rest api but the rest api only allows up to a maximum of 1,000 results at a time. So if i have, lets say 1,500 total, i want to make a request to get the first 1,000 then i need to get the rest with another almost exact request , except the startAt: parameter is different(so i could go from 1001 - 1500. I want to set this up in a while loop(while i am done loading all the data) and am just reading about semaphores but its not working out like I expected it to. I don't know how many results I have until i make the first request. It could be 50, 1000, or 10,000.
here is the code:
while(!finishedLoadingAllData){
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURLRequest *myRequest = [self loadData: startAt:startAt maxResults:maxResults];
[NSURLConnection sendAsynchronousRequest:myRequest
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if(error){
completionHandler(issuesWithProjectData, error);
}
else{
NSDictionary *issuesDictionary = [[NSDictionary alloc] initWithDictionary:[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error]];
[issuesWithProjectData addObjectsFromArray:issuesDictionary[#"issues"]];
if(issuesWithProjectData.count == [issuesDictionary[#"total"] integerValue]){
completionHandler([issuesWithProjectData copy], error);
finishedLoadingAllData = YES;
}
else{
startAt = maxResults + 1;
maxResults = maxResults + 1000;
}
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
Basically I want to keep the while loop waiting until the completion block finished. Then and only then do i want the while loop to check if we have all of the data or not(and if not, make another request with the updated startAt value/maxResults value.
Right now it just hangs on dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
What am i doing wrong or what do i need to do? Maybe semaphores are the wrong solution. thanks.
Ok. The more I look, the more I don't think its a bad idea to have semaphores to solve this problem, since the other way would be to have a serial queue, etc. and this solution isn't all that more complicated.
The problem is, you are requesting the completion handler to be run on the main thread
[NSURLConnection sendAsynchronousRequest:myRequest
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
and you are probably creating the NSURL request in the main thread. Hence while it waits for the semaphore to be released on the mainthread, the NSURL completion handler is waiting for the mainthread to be free of its current run loop. So create a new operation queue.
would it not be easier to do something like this instead:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //run on a background thread
while(!finishedLoadingAllData){
NSURLRequest *myRequest = [self loadData: startAt:startAt maxResults:maxResults];
NSHTTPURLResponse *response = nil;
NSError *error = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:myRequest returningResponse:&response error:&error]; //blocks until completed
if(response.statusCode == 200 && responseData != nil){ //handle response and set finishedLoadingAllData when you want
//do stuff with response
dispatch_sync(dispatch_get_main_queue(), ^{
//do stuff on the main thread that needs to be done
}
}
});
Please dont do that.. NSURLConnection sendAsynchronousRequest will be loading itself in loop for you, if your data is in chunk.. try this instead..
__block NSMutableData *fragmentData = [NSMutableData data];
[[NSOperationQueue mainQueue] cancelAllOperations];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
[fragmentData appendData:data];
if ([data length] == 0 && error == nil)
{
NSLog(#"No response from server");
}
else if (error != nil && error.code == NSURLErrorTimedOut)
{
NSLog(#"Request time out");
}
else if (error != nil)
{
NSLog(#"Unexpected error occur: %#", error.localizedDescription);
}
else if ([data length] > 0 && error == nil)
{
if ([fragmentData length] == [response expectedContentLength])
{
// finished loading all your data
}
}
}];
I've created two chunky json response from server handling method.. And one of them is this, so hope this will be useful to you as well.. Cheers!! ;)

can Mainqueue do background task while using in NSURLConnection?

1)When we use mainQueue in NSURLConnection for sendasynchronousrequest ,How it gets response through running in background?because mainqueue is for updating UI only.For EX:
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if ([data length] > 0 && error == nil)
[delegate receivedData:data];
else if ([data length] == 0 && error == nil)
[delegate emptyReply];
else if (error != nil && error.code == ERROR_CODE_TIMEOUT)
[delegate timedOut];
else if (error != nil)
[delegate downloadError:error];
}];
When you say mainQueue is for updating UI only is not strictly right.
The exact point is that mainQueue is used to update UI and so developers avoid to do any other tasks on the mainQueue to no introduce delays on the UI. It's not completely forbidden.
But, doing asynchronous task like a network request on the mainQueue is really a bad idea because it will introduce an unbounded delay that will completely break the smoothness of your UI
To not block your mainQueue you should execute your request this way :
Create your own application operation queue
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
then reuse your operationQueue anywhere you need it to execute your network requests
[NSURLConnection sendAsynchronousRequest:urlRequest queue:operationQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ([data length] > 0 && error == nil)
[delegate receivedData:data];
else if ([data length] == 0 && error == nil)
[delegate emptyReply];
else if (error != nil && error.code == ERROR_CODE_TIMEOUT)
[delegate timedOut];
else if (error != nil)
[delegate downloadError:error]; }];
Another alternative, safer and more effective is to use AFNetworking framework

Set UILabel text within code block

I have a code block for my NSURLConnection sendAsynchronousRequest, and when I try to set a UILabel's text, the text is never set, even though the values are there. Here's my code:
NSString *address = [addresses objectAtIndex:indexPath.row];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://myurl.com/%#", address]]];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ([data length] > 0 && error == nil)
{
NSString *dataOfData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if(![dataOfData isEqualToString:#"ERROR: address invalid"]) {
[balanceLabel setText:[NSString stringWithFormat:#"Balance: %#", dataOfData]];
if(data) {
qrCodeButton.alpha = 1;
}
} else {
errorLabel.text = #"This address is invalid.";
}
}
else if ([data length] == 0 && error == nil)
{
NSLog(#"Nothing was downloaded.");
[balanceLabel setText:#"Server Error, Please Try Again"];
}
else if (error != nil){
NSLog(#"Error = %#", error);
}
}];
Why is the UILabel's text never set? Is there a limitation to code blocks? If so, how would I fix my problem? Cheers!
It is because an NSOperationQueue is not the main thread. What you're doing is illegal. And the sad thing is that there is no need for it! Just say:
[NSURLConnection sendAsynchronousRequest:urlRequest
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// ... and the rest exactly as you have it now
All fixed. Your request is asynchronous on a background thread, but when it comes back to you on the completion handler, you'll be on the main thread and able to talk to the interface etc.
Your code operates UI element should execute on main thread.
dispatch_async(dispatch_get_main_queue(), ^{
if ([data length] > 0 && error == nil)
{
NSString *dataOfData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if(![dataOfData isEqualToString:#"ERROR: address invalid"]) {
[balanceLabel setText:[NSString stringWithFormat:#"Balance: %#", dataOfData]];
if(data) {
qrCodeButton.alpha = 1;
}
} else {
errorLabel.text = #"This address is invalid.";
}
}
else if ([data length] == 0 && error == nil)
{
NSLog(#"Nothing was downloaded.");
[balanceLabel setText:#"Server Error, Please Try Again"];
}
else if (error != nil){
NSLog(#"Error = %#", error);
}
}) ;
Make sure errorLabel is not nil and the UILabel is visible (It is added in the view hierarchy and its frame is appropriate).

Dynamically reference variables/objects/arrays etc

This seems such an easy task, at least it is in VB.net. I simply need to reference an array based on a string that is passed to a method. When a view controller loads a method is called and a string is passed. A URL will be created based on this string and JSON will be fetched from it. What I want is for the method to populate an appropriate array based on this passed string.
Here we see the method "goGetData" being called in class "getData" with one of three string parameters "workshop/speaker/exhibitor":
- (void)viewDidLoad
{
[getData goGetData:#"workshop"];
[getData goGetData:#"speaker"];
[getData goGetData:#"exhibitor"];
getData *getDataInstance = [[getData alloc] init];
NSArray *newTablesArray = getDataInstance.jsonAllTables;
NSLog(#"Json currently = %#", newTablesArray);
[super viewDidLoad];
[[self myTableView]setDelegate:self];
[[self myTableView]setDataSource:self];
arrayTable =[[NSMutableArray alloc]init];
}
For example if "goGetDate" is fired with "speaker" I would need the speaker data to be fetched and then the "_jsonSpeaker" array to be populated. Here is my attempt so far to reference and populate the arrays based on what string was passed in the method call:
#import "getData.h"
#implementation getData
+(void)goGetData:(NSString *)requestedTable
{
getData *getDataInstance = [[getData alloc] init];
[getDataInstance buildArray];
[getDataInstance fetchData:requestedTable];
}
-(void)buildArray{
// I tried putting the arrays in an array but still do no know how to reference them
_jsonAllTables = [[NSMutableArray alloc] initWithObjects:_jsonExhibitor, _jsonSpeaker, _jsonWorkshop, nil];
}
-(void)fetchData:(NSString *)requestedTable{
NSString *varCurrentTable;
varCurrentTable = [NSString stringWithFormat:#"_json%#", requestedTable];
NSString *requestedURL;
requestedURL = [NSString stringWithFormat:#"http://testapi.website.com/api/%#", requestedTable];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:requestedURL]];
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (response){
NSHTTPURLResponse *newResp = (NSHTTPURLResponse*)response;
if (newResp.statusCode == 200) {
// STUFF FOR TESTING NSLog(#"Response to request: %# is: %i GOOD", requestedURL, newResp.statusCode);
if ([data length] >0 && error == nil)
{
// STUFF FOR TESTING NSUInteger indexOfArray = [_jsonAllTables indexOfObject:varCurrentTable];
// STUFF FOR TESTING NSString *objectAtIndexOfArray = [_jsonAllTables objectAtIndex:indexOfArray];
// This is the part I think I am stuck on:
// "CURRENT TABLE TO BE POPULATED" = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
}
else if ([data length] == 0 && error == nil)
{
NSLog(#"Nothing was downloaded");
}
else if (error != nil)
{
NSLog(#"Error: %#", error);
}
} else if (newResp.statusCode == 404){
NSLog(#"Response to request: %# is: %i BAD - URL incorrect", requestedURL, newResp.statusCode);
} else {
// add more returned status error handling here
}
}else{
NSLog(#"No response received");
}
}];
}
#end
Thanks,
Added for clarification on what I am trying to achieve: To save a LOT of writing out the same thing over and over is the following possibly in Obj-c (please excuse the mish-mash of languages)
NSArray *ListOfTables = [NSArray arrayWithObjects:#"Speaker", #"Exhibitor", #"Workshop", nil];
For i as int = 0 to ListOfTables.count{
[self fetchData:(ListOfTables.objectAtIndex = i) withCompletion:^(NSArray* objects, NSError*error){
dispatch_async(dispatch_get_main_queue(), ^{
if (objects) {
self.(ListOfTables.objectAtIndex = i) = objects;
}
else {
NSLog(#"Error: %error", error);
}
});
}];
i++;
Next
};
Notice i don't call a separate method for each table, instead I call the same method but with different table name parameter each time. I can't seem to find a working example with such placeholders in Xcode.
You probably want a method which is asynchronous and returns the result via a completion handler:
typedef void(^completion_t)(NSArray* objects, NSError*error);
-(void)fetchData:(NSString *)tableName
withCompletion:(completion_t)completionHandler;
Usage:
- (void) foo {
[self fetchData:tableName1 withCompletion:^(NSArray* objects, NSError*error){
dispatch_async(dispatch_get_main_queue(), ^{
if (objects) {
self.table1 = objects;
}
else {
NSLog(#"Error: %error", error);
}
});
}];
[self fetchData:tableName2 withCompletion:^(NSArray* objects, NSError*error){
dispatch_async(dispatch_get_main_queue(), ^{
if (objects) {
self.table2 = objects;
}
else {
NSLog(#"Error: %error", error);
}
});
}];
[self fetchData:tableName3 withCompletion:^(NSArray* objects, NSError*error){
dispatch_async(dispatch_get_main_queue(), ^{
if (objects) {
self.table3 = objects;
}
else {
NSLog(#"Error: %error", error);
}
});
}];
}
Implementation:
typedef void(^completion_t)(NSArray* objects, NSError* error);
-(void)fetchData:(NSString *)tableName
withCompletion:(completion_t)completionHandler
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:tableName]];
// Setup HTTP headers, e.g. "Accept: application/json", etc.
...
[NSURLConnection sendAsynchronousRequest:request
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSError* err = error;
NSArray* objects; // final result array as a representation of JSON Array
if (response) {
NSHTTPURLResponse *newResp = (NSHTTPURLResponse*)response;
if (newResp.statusCode == 200) {
if ([data length] >0 && error == nil)
{
NSError* localError;
objects = ... // Use NSJSONSerialization to obtain a representation
if (objects) {
if (completionHandler) {
completionHandler(object, nil);
}
return;
}
else {
err = localError;
}
}
else {
err = ...
}
}
}
if (objects == nil) {
assert(err);
if (completionHandler) {
completionHandler(nil, err);
}
}
}];
}
Asynchronous Loop
Another example, for loading a bunch of data:
First, implemented a method which is an "asynchronous loop":
typedef void(^completion_t)(id result, NSError* error);
- (void) fetchObjectsWithTableNames:(NSMutableArray*)tableNames
completion:(completion_t)completionHandler;
This method is, itself asynchronous, thus the completion handler.
Usage:
- (void) foo
{
NSArray* tableNames = #[#"A", #"B", #"C"]; // possibly 1000
[self fetchObjectsWithTableNames:[tableNames mutableCopy]:^(id result, NSError*error){
if (error) {
NSLog(#"Error: %#", error);
}
else {
// finished fetching a bunch of datas with result:
NSLog(#"Result: %#", result);
}
}];
}
Implementation
- (void) fetchObjectsWithTableNames:(NSMutableArray*)tableNames
completion:(completion_t)completionHandler;
{
if ([tableNames count] > 0) {
NSString* name = [tableNames firstObject];
[tableNames removeObjectAtIndex:0];
[self fetchData:name withCompletion:^(NSArray* objects, NSError*error){
if (objects) {
[self.tables addObject:objects];
[self fetchObjectsWithTableNames:tableNames completion:completionHandler];
}
else {
// handle error
}
}];
}
else {
// finished
if (completionHandler) {
completionHandler(#"finished", nil);
}
}
}

iOS Downloading image in background and updating the UI

I am trying update an imageview with new image for every 1 sec by downloading the image from the server. The downloading happens in background thread. Below shown is the code
NSTimer *timer = [NSTimer timerWithTimeInterval:0.5
target:self
selector:#selector(timerFired:)
userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
-(void)timerFired:(id)sender
{
NSURLRequest *request=[[NSURLRequest alloc]initWithURL:[NSURL
URLWithString:#"http://192.168.4.116:8080/RMC/ImageWithCommentData.jpg"]];
NSOperationQueue *queueTmp = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queueTmp completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if ([data length] > 0 && error == nil)
{
[self performSelectorOnMainThread:#selector(processImageData:) withObject:data waitUntilDone:TRUE modes:nil];
}
else if ([data length] == 0 && error == nil)
{
}
else if (error != nil)
{
}
}];
}
-(void)processImageData:(NSData*)imageData
{
NSLog(#"data downloaded");
[self.hmiImageView setImage:[UIImage imageWithData:imageData]];
}
My image is getting downloaded. But ProcessImageData method is not called. Please help me to fix this issue.
The problem is you are calling NSURLConnection asynchronously :
[NSURLConnection sendAsynchronousRequest:request queue:queueTmp completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
so before you get any data the : if ([data length] > 0 && error == nil) get called .
so the data length remains 0 thats why your method is not called . for achieving your requirement you can do like :
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^(void) {
NSData *imageData = //download image here
UIImage* image = [[UIImage alloc] initWithData:imageData];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
//set image here
}
});
}
});

Resources