In my code I am calling this
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if (responseData != nil) {
NSError *error = nil;
NSArray *dataSource = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableLeaves error:&error];
[self.ticker1 loadData:dataSource];
} else {
NSLog(#"Error requesting timeline %# with user info %#.", error, error.userInfo);
}
}];
and in the loadData: method I do this
NSDictionary *dict = [dataSource objectAtIndex:0];
_label.text = [dict objectForKey:#"text"];
dispatch_queue_t queue = dispatch_queue_create("com.test.ios.task", NULL);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSURL *imageURL = [NSURL URLWithString:[[dict objectForKey:#"user"] objectForKey:#"profile_image_url"]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
dispatch_async(main, ^{
_image.image = [UIImage imageWithData:imageData];
});
});
dispatch_release(queue);
The problem is the image I load on main queue loads much faster than _label.text is being set. It gets set after a long delay of about 4-5 seconds. I would like to know why this is happening. Is it because the main queue is not being released or something on that lines?
I can't tell from your code which thread the request's handler block is being called on, but if it's not the main thread, that could be the problem.
Try setting the label's text property from within a
dispatch_async(main, ^{
_label.text = [dict objectForKey:#"text"];
});
block.
The weirdest thing fixed this problem for me. I made the label hight fixed and slightly bigger then it should be, in the storyboard. It made a yellow warning in the constraints, but at least the label is loading.
Related
I have some code that gets an image from a web page and displays it in an ImageView. But the image loads very slowly for some reason I don't really understand! Through my logging I can see that all the data for the image (base64 string) arrives pretty instantly, yet it takes about 12 - 15 seconds for the image to appear in the ImageView.
I find this very strange because I used an NSStream to get the data for the image in a different method and the image loaded as soon as all the data arrived. But with this URLSession method its taking longer for the image to load. This doesn't really make sense! This method shouldn't affect how the ImageView loads that data.
Has anybody any ideas why this might be happening?
heres the code:
- (void)postMethod:(NSDictionary *)numDict
{
NSURL *url = [NSURL URLWithString:#"http://theWebAddress.com/aPage.php"]; // add url to page
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
request.HTTPMethod = #"POST";
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:numDict options:kNilOptions error:&error];
NSLog(#"%#", numDict);
if (!error)
{
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:data completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *diction = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
for (id key in diction)
{
if ([key isEqualToString:#"text"])
{
NSLog(#"data is text");
self.messageLabel.text = diction[#"text"];
break;
}
else if ([key isEqualToString:#"image"])
{
NSLog(#"data is an image");
// gets the base64 string pretty instantly but takes 12 - 15 seconds to pop up in the imageView
NSData *ImgData = [[NSData alloc] init];
ImgData = [[NSData alloc] initWithBase64EncodedString:diction[#"image"] options:1];
self.ImageView.image = [UIImage imageWithData:ImgData];
break;
}
}
}];
[uploadTask resume];
}
}
many thanks!
Your completion handler might be operating on a background thread. UI updates should always work on the main thread. Put a break point at
self.ImageView.image = [UIImage imageWithData:ImgData];
and see if it is on the main thread. If not, dispatch it to the main thread before you set the ImageView.image:
dispatch_async(dispatch_get_main_queue(), ^{
self.ImageView.image = [UIImage imageWithData:ImgData];
});
You can try to use SDWebImage https://github.com/rs/SDWebImage and all you need is to set the image in imageView like this:
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
You are firstly downloading image and then showing image.You can download image by using lazy loading.
For this you can use EgoImageView not uiimageview.
self.ImageView.imageURL=[NSURL URLWithString:
here self.ImageView is of egoimageview type.
you can get this class from github.
https://github.com/enormego/EGOImageLoading
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
Im trying to set a UITextView's text property with this code but I get a crash saying I can't do it from the main thread:
__block NSString *stringForText;
self.uploadTask = [upLoadSession uploadTaskWithRequest:request fromData:body completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// ...
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*) response;
int errorCode = httpResponse.statusCode;
NSString *errorStatus = [NSString stringWithFormat:#"%d",errorCode];
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSString *totalResponse = [errorStatus stringByAppendingString:responseString];
stringForText = totalResponse;
[self updateView:stringForText];
// 4
self.uploadView.hidden = NO;
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
}];
// 5
[_uploadTask resume];
}
-(void)updateView:(NSString*)texto{
self.myTextView.text = texto;
}
Why does it crash saying I can't call it from the main thread in TestFlight?
Check again. You must update the UI from the main thread. It is likely your upload completion handler is operating off of the main thread. If you would like to call updateView: from any thread, then the you can dispatch the manipulation of the label to the main thread:
-(void)updateView:(NSString*)texto{
dispatch_async(dispatch_get_main_queue(), ^{
self.myTextView.text = texto;
});
}
Make sure that you're doing all of your work on the main thread. Once the upload completes, dispatch into the main queue:
__block NSString *stringForText;
self.uploadTask = [upLoadSession uploadTaskWithRequest:request fromData:body completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
// ...
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*) response;
int errorCode = httpResponse.statusCode;
NSString *errorStatus = [NSString stringWithFormat:#"%d",errorCode];
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSString *totalResponse = [errorStatus stringByAppendingString:responseString];
stringForText = totalResponse;
[self updateView:stringForText];
// 4
self.uploadView.hidden = NO;
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
});
}];
// 5
[_uploadTask resume];
It's crashing because of exactly that—your block is being called on a background thread and UI elements need to be modified on the main thread.
Wrap [self updateView:stringForText]; like dispatch_sync(dispatch_get_main_queue(), ^{ [self updateView:stringForText]; });
I have an URL, which when copied into a browser, displays an image. My function is supposed to download the image asynchronously.
- (UIImage *)downloadImage:(NSString *)imageURL
{
NSError *error = nil;
NSURL *urlString = [NSURL URLWithString:imageURL];
NSData *data = [NSData dataWithContentsOfURL:urlString options:NSDataReadingUncached error:&error];
__block UIImage *image;
if (!error) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
image = [UIImage imageWithData:data];
});
return image;
} else {
NSLog(#"%#", [error localizedDescription]);
}
return nil;
}
When I try to display the image in an UIImageView I get no errors, no nothing. I have NSLogged out both data and the imageURL passed in, and none of those are empty.
Any suggestions?
By calling dispatch_async, you're scheduling that work to happen later. Your function exits with nil before that work is done. You'll want to add a callback block to your function or make it block until you receive and process the image data.
Here is an example of a function with a block callback and how to use it.
- (void)downloadImageAtURL:(NSString *)imageURL withHandler:(void(^)(UIImage *image))handler
{
NSURL *urlString = [NSURL URLWithString:imageURL];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSError *error = nil;
NSData *data = [NSData dataWithContentsOfURL:urlString options:NSDataReadingUncached error:&error];
if (!error) {
UIImage *downloadedImage = [UIImage imageWithData:data];
handler(downloadedImage); // pass back the image in a block
} else {
NSLog(#"%#", [error localizedDescription]);
handler(nil); // pass back nil in the block
}
});
}
- (void)keyboardDidShow:(NSNotification *)aNotification {
[self downloadImageAtURL:#"" withHandler:^(UIImage *image) {
if (image) {
// display
} else {
// handle probelm
}
}];
}
The call to dataWithContentsOfURL:options:error: needs to be within the dispatch_queue block for it to be asynchronous. Any changes to the UI need to be in the mainThread. It should look something like this:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^(void) {
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage* image = [[UIImage alloc] initWithData:imageData];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
//load image into UIImageView
}
});
}
});
You are not downloading the image asynchronously. Moreover, a method that is supposed to return a value in an async way cannot return that value through the method return value, but it should return it using a block.
You can try to do something like this:
- (void)downloadImage:(NSString *)imageURL onComplete:(void (^)(UIImage *, NSError * error))onComplete
{
NSURL *urlString = [NSURL URLWithString:imageURL];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSError *error = nil;
NSData *data = [NSData dataWithContentsOfURL:urlString options:NSDataReadingUncached error:&error];
image = [UIImage imageWithData:data];
if (onComplete) {
// Keep in mind that onComplete block will be called on a background thread.
// If you need to use it on UIImageView, you must set it on main thread.
onComplete(image, error);
}
});
}
Then, when you need to set the UIImageView image:
__weak typeof(self)selfB = self; // Better to use a weak reference inside blocks to avoid retain cycles
[self downloadImage:myURLString onComplete:^(UIImage * image, NSError * error) {
dispatch_async(dispatch_get_main_queue(), ^{ // As you can see, we use main thread for UI updates
selfB.imageView.image = image;
});
}];
In my view I attempt to display some weather related info. I use
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
Make NSURL and other related things
Send NSURLRequest
Set 2 UILabels to data
NSLog(Show data retrieved)
});
For some reason, I see the NSLog line roughly 15-45 seconds before the uilabel changes to the new text. I am fairly new to Obj-C and a lot of my code comes from using tutorial so I dont have the greatest understanding of the dispatch_async method. Any thoughts or suggestions would be appreciated.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURLResponse *response = nil;
NSError *error = nil;
NSData *respData = nil;
NSURLRequest *forecastRequest = [NSURLRequest requestWithURL:currentForecastUrl];
respData = [NSURLConnection sendSynchronousRequest:forecastRequest returningResponse:&response error:&error];
[_responseData appendData:respData];
NSLog(#"Response: %#", response);
NSLog(#"Error: %#", error);
id JSON = [_responseData yajl_JSON];
currentForecast = [[NSArray alloc]initWithObjects:[JSON objectForKey:#"temperature"],[JSON objectForKey:#"feelsLike"],nil];
[_temperatureLabel setText:[NSString stringWithFormat:#"%#",[JSON objectForKey:#"temperature"]]];
[_tempDescriptionLabel setText:[NSString stringWithFormat:#"%#",[JSON objectForKey:#"desc"]]];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:2.0];
[_tempDescriptionLabel setAlpha:1];
[_temperatureLabel setAlpha:1];
[UIView commitAnimations];
NSLog(#"Done");
NSLog(#"JSON: %#", [JSON yajl_JSONStringWithOptions:YAJLGenOptionsBeautify indentString:#" "]);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
is used for create a background thread, but the user interface should be updated in the Main Thread
like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//do stuff in background
dispatch_async(dispatch_get_main_queue(), ^{
// Update your UI
});
});
All the "use the main queue" answers are good. But please notice that NSURLConnection does all this for you.
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// this happens on the main thread because we passed mainQueue
}];
EDIT: Here's your code, except smaller and with modern syntax. It should do exactly the same job. For fun, cut and paste this in.
// it assumes the following variables in scope
NSURL *currentForecastUrl;
__block NSArray *currentForecast;
UILabel *_temperatureLabel, *_tempDescriptionLabel;
// start counting lines here
NSURLRequest *forecastRequest = [NSURLRequest requestWithURL:currentForecastUrl];
[NSURLConnection sendAsynchronousRequest:forecastRequest queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// I cheated a little on lines, because we really ought to check errors
// in here.
NSError *parseError; // use native JSON parser
id parse = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&parseError];
currentForecast = #[parse[#"temperature"], parse[#"feelsLike"]]; // note the array and dictionary literal syntax
_temperatureLabel.text = parse[#"temperature"]; // if these are ivars, I reccomend using self.temperatureLabel, not _temp...
_tempDescriptionLabel.text = parse[#"desc"];
[UIView animateWithDuration:2.0 animations:^{ // use UIView animation block
_temperatureLabel.alpha = 1.0;
_tempDescriptionLabel.alpha = 1.0;
}];
}];
Deleting code for me is the most pleasurable part of programming. The line of code that's easiest to read, executes the quickest and requires no testing is the line that isn't there.
Your problem is because you're trying to update the UILabels off the main thread. I've seen that exact symptom before, where updating UI elements off the main thread still updates them, but with a long delay. So any time you have a UI element that's behaving in this way, check to make sure it's executing on the main thread.
I am surprised this code works at all because you are updating the label on a background thread. You want to do something along these lines:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul), ^{
//Make NSURL and other related things
//Send NSURLRequest
dispatch_async(dispatch_get_main_queue(), ^{
// Set your UILabels
});
});
I have an app that has a tableviewcontroller with this viewDidLoad:
- (void)viewDidLoad{
[super viewDidLoad];
// begin animating the spinner
[self.spinner startAnimating];
[SantiappsHelper fetchUsersWithCompletionHandler:^(NSArray *users) {
self.usersArray = [NSMutableArray array];
for (NSDictionary *userDict in users) {
[self.usersArray addObject:[userDict objectForKey:#"username"]];
}
//Reload tableview
[self.tableView reloadData];
}];
}
The Helper Class method is this:
+(void)fetchUsersWithCompletionHandler:(Handler)handler {
NSString *urlString = [NSString stringWithFormat:#"http://www.myserver.com/myApp/fetchusers.php"];
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
[request setHTTPMethod: #"GET"];
__block NSArray *usersArray = [[NSArray alloc] init];
//A
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// Peform the request
NSURLResponse *response;
NSError *error = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error) {
// Deal with your error
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
NSLog(#"HTTP Error: %d %#", httpResponse.statusCode, error);
return;
}
NSLog(#"Error %#", error);
return;
}
NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
usersArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];
if (handler){
dispatch_async(dispatch_get_main_queue(), ^{
handler(usersArray);
});
}
});
}
The above code was suggested to me and it makes sense from what I know about GCD. Everything runs on the main queue, but before it dispatches to a background queue before the NSURLConnection synchronous call. After it gets the data it fills the usersArray and should return it to the main queue. The usersArray is populated and when it tests for if handler, it moves to the dispatch_asynch(dispatch_get_main_queue () line. But when it returns to the main queue to process the array dictionaries, the NSArray *users is empty. The app crashes with this error:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
If I comment out the dispatch_async(dispatch_get_main_queue() code to look like this:
if (handler){
//dispatch_async(dispatch_get_main_queue(), ^{
handler(usersArray);
//});
}
It works fine...well kinda, its a little sluggish. Why is this failing?
Replacing
dispatch_async(dispatch_get_main_queue(),
With:
dispatch_sync(dispatch_get_main_queue(),
REASON:
dispatch_sync will wait for the block to complete before execution