iPhone - GCD doesn't work for the first time - iphone

In my app, I load image from the URL:
-(void)loadImages
{
...
image1 = [UIImage imageWithData:[NSData dataWithContentsOfURL:imgUrl1]];
}
In order to avoid blocking the main thread until the download has completed I call this method in
-viewDidAppear using GCD:
dispatch_async( dispatch_get_global_queue(0,0), ^{
[self loadImages];
});
However, when I open my view controller with the imageView at the first time, the imageView is empty (even if I wait for a long time) but after I open this view controller again and image appears and everything is good to go.
Where is my mistake ?
Sorry, new to multithreading :)
EDIT:
I also forgot to mention, that I use the image in the tableView when I get it:
cell.imageView.image = image1;

This may not be the answer that you were looking for, but that's not the recommended way to load URLs. You should use the URL loading classes available such as NSURLRequest and NSURLConnection.
Try this:
NSURLRequest *imageRequest = [[NSURLRequest alloc] initWithURL:imageURL];
[NSURLConnection sendAsynchronousRequest:imageRequest queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
UIImage *image = [[UIImage alloc] initWithData:data];
[imageView setImage:image];
}
}];

UIElements in iOS should always be updated via main thread.
What you could do is:-
__block NSData *data;
dispatch_queue_t myQueue = dispatch_queue_create("com.appName", NULL);
dispatch_async(myQueue, ^{
data = [NSData dataWithContentsOfURL:imgUrl1];
dispatch_async(dispatch_get_main_queue(), ^(void) {
cell.imageView.image = [UIImage imageWithData = data];
});
});
or Else you there is a better way of fetching images from URL by using AFNetworking. It is faster and easier.
You just have to write one line of code:-
[cell.imageView setImageWithURL:imgUrl1];

You have many problems here:
You can not start networking requests on an thread not running a runloop.
You can not update your UI from a thread other than the main thread.
[NSData dataWithContentsOfURL:imgUrl1] is not a safe way to load an external resource, even on a different thread (but especially on the main thread).
Any time you dispatch to a different thread, you run the risk that your table cell has been recycled and is no longer showing the data you think it is. (It's still the same cell instance, but is now showing some other row's data.)
What you should be doing:
Start your network operation using asynchronous calls on the main thread. (You can use another thread or queue if you want, but you need to make sure it's running a runloop.)
From your delegate messages, dispatch your image decoding on a different thread.
After the image is decoded, dispatch back to the main thread to update it.
Before actually assigning the image, check that the cell is still being used for the purpose you think.
You can solve the first three problems by using AFNetworking. It wraps the delegate methods and lets you just provide a success and failure block. AFNetworking's AFImageRequestOperation in particular bounces code between queues as I've described. (It even runs its main networking loop in a different thread, which isn't necessary but since it does it well, why not?)
You'll still need to verify the cell's identity.

You need to inform the view that the image needs to be redrawn. Add:
[imageView setNeedsDisplay];
to the end of your loadImages method.

Since you are using it in TableView, add [self.tableView reloadData]; in the end of loadImages method.

Related

Loading Facebook Images in the background to save to Core Data on the main thread

I am loading Facebook images. I am trying to use the code below to return an image so that I can save it to the record of a 'Friend' object using Core Data. It isn't working.. nothing gets returned. Is there any way to load the data in the background and then create the image on the main queue, and return it? I need my image created on the main queue because I want to save it to my object with my app delegate's managed object context.
+ (UIImage *)imageForObject:(NSString *)objectID {
NSString *url = [[NSString alloc] initWithFormat:#"https://graph.facebook.com/%#/picture?width=%d&height=%d",objectID,IMAGE_MAXWIDTH,IMAGE_MAXHEIGHT];
__block UIImage *image;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
dispatch_async(dispatch_get_main_queue(), ^{
image = [[UIImage alloc] initWithData:data];
});
});
return image;
}
Sure, it's possible to load images in the background, but not like this. This method is designed so that it's really impossible for it to not return nil. When you call dispatch_async, it returns immediately and finishes its work asynchronously. That's why it has "async" right in its name. You code reaches return image; before you've loaded the image.
You're on the right track, though. The usual approach is to dispatch back to the main queue when the background work is finished. But when doing that, it's impossible for a method like yours to return the image. If it did, the main queue would have to wait around until the image was loaded, which defeats the whole purpose of running on a background queue.
What you should do instead is:
In your dispatch_async back to the main queue, do whatever work you need to do with the image-- show it on the screen, or write it to a file, or whatever it is you need to do with the image.
Make your method return void, since it can't return the image directly to the caller. Or if you like, make it return BOOL to indicate whether the image load was started. But it can't return the UIImage unless you make the main queue wait on the image-- which as I mentioned, defeats the whole purpose of using background queues.

On iOS, how to make a cell.imageView refresh its content?

I am experimenting in tableView:cellForRowAtIndexPath to set an image by calling
[self downloadImage:urlString andSetIntoCellImageView:cell]
and in downloadImage, it will call NSURLConnection:sendAsynchronousRequest (iOS 5 and up only), and in the completion block, set the image by using
cell.imageView.image = [UIImage imageWithData:data]; // data is downloaded data
and it works if in tableView:cellForRowAtIndexPath, the imageView is populated with a dummy placeholder image -- and I wonder how the new image is refreshed, is it by setNeedsDisplay to do a repaint? But if I don't set the placeholder image, then the new image won't show at all. I wonder what mechanism can be used to make it show the image?
If I use
[cell.imageView setNeedsDisplay]
or
[cell setNeedsDisplay];
in the completion block, it won't work, and if I use
[self.table reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
in the completion block by making downloadImage accept the indexPath, it will call tableView:cellForRowAtIndexPath again, and cause an infinite loop. So it seems like I need to use some hash table to remember if the image is already in hash table: if not, call downloadImage, and if in hash table, simply use it, so there will be no infinite loop.
But is there an easy way to cause the image to show up? Setting a placeholder image works but what if we don't -- by what mechanism does placeholder cause the refresh of image?
When a UITableViewCell's -layoutSubviews method is called, if its imageView's image property is nil, imageView is given a frame of (0,0,0,0). Also, -layoutSubviews only is to be called in some situations: when the cell is about to become visible and when it is selected. Not during normal scrolling. So what you've seen is that setting the placeholder inside tableView:cellForRowAtIndexPath: sizes cell.imageView to a non-zero size and subsequent changes of the image will be visible.
I fixed the issue by calling [cell setNeedsLayout] in the completion handler, like so:
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:MY_IMAGE_URL]];
[NSURLConnection sendAsynchronousRequest:request
queue:self.operationQueue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIImage *image = [UIImage imageWithData:data];
cell.imageView.image = image;
[cell setNeedsLayout];
}];
I found the completion block happens in the background so that necessitates performing my UI work on the main thread. Of course this solution won't account for cell reuse and so forth, but at least solves why the cell's image wouldn't appear :)
Hope this helps!
This doesn't answer your question directly, but a simple workround to your problem would be to use SDWebImage in your cellForRowAtIndexPath: instead. The example on their README page does exactly what you are trying to do.

How to get notified about imageWithContentsOfFile: completion?

I load a huge-huge image with imageWithContentsOfFile:, so I have to set up an activityIndicator during the process.
Is there any way/any delegate callback I can use to be informed about the end of this loading process?
imageWithContentsOfFile is synchronous.
You could start an activity indicator, load your big image into memory in a background thread and then go back to the main thread and stop the indicator.
- (void)loadBigImage {
[activityIndicator startAnimating];
[self performSelectorInBackground:#selector(loadBigImageInBackground) withObject:nil];
}
- (void)loadBigImageInBackground {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *img = [UIImage imageWithContentsOfFile:#"..."];
[self performSelectorOnMainThread:#selector(bigImageLoaded:) withObject:img waitUntilDone:NO];
[pool release];
}
- (void)bigImageLoaded:(UIImage *)img {
[activityIndicator stopAnimating];
// do stuff
}
Short answer: Nope. sorry!
Long answer :
You could open the file in a background process (an NSOperation?) bit by bit using C style methods i.e. fopen, fread etc) and fire notifications back to the main thread during the load. Then create the image and fire a notification that the image is ready?
If you want to have a delegate & be informed of the progress of the load, you can use an NSURLConnection instead of the synchronous imageWithContentsOfFile.
There's an example of this in the Apple URL Loading System Programming Guide
Your NSURLConnection delegate didReceiveData: method could append the incoming data to an NSData object, then you would use UIImage imageWithData: to create them image once everything's downloaded.
This gives you the most flexibility/control over monitoring the progress of the load; although if all you're trying to do is avoid hanging the UI while the image downloads, simply using imageWithContentsOfFile in a background thread may be easier.

memory leak issue

I am loading images to UITableViewCell using the following function. But when I run my app with Debugger its getting crashed at [pool release] whenever I scroll my UITableView. What might I do to solve this? Thanks in advance.
- (void) loadImage{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
imageURL = [imageURL stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
//NSLog(#"img url cell**** %#",imageURL);
self.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
//self.image = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]];
[pool release];
}
By the fact that you are using a NSAutoreleasePool I guess that load image is running in a thread that is not the main thread. Is this Correct? If that is the case you are making a UIKit invocation (self.image = ...) in this non-main thread and this is a possible source of the crash you are experiencing. All UIKit updates must be made in the main thread, since UIKit is not thread safe. Try replacing:
self.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
by
[self performSelectorOnMainThread:#selector(setImage:) withObject:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]] waitUntilDone:YES];
Notice I'm guessing the name of the setter is setImage:, you may need to correct if the setter selector has a different name that.
First of all, it is recommended to use [pool drain] instead of [pool release].
But I see another potential issue here: according to your code, you defined initially imageURL outside of your newly created autorelease pool (I don't see any alloc/init for imageURL, where is coming from? is it a table cell instance?) then you reassign imageURL with a newly allocated and autoreleased string: [imageURL stringByAddingPercentEscapesUsingEncoding:...]. Finally when you release the pool the new imageURL is release, so at the end what you had is a leak of the previously allocated imageURL. I can expect at this point that when exiting from the inner autorelease pool the run loop autorelease tries to release imageURL again (for example, but I need to see your code to fully understand) which has been deallocated.
Can you try to assign the result of stringByAddingPercentEscapesUsingEncoding: to a different name (e.g. myImageURL)?
Quite a few things wrong here.
Firstly, your need of an autorelease pool quite clearly suggests that you're not doing this work on the main thread. UIKit is not thread-safe. Any UI work needs to be done on the main thread or undefined behaviour will occur, such as crashes.
Secondly, you're using what looks like a synchronous URL download of an image. Correct me if you're loading it from a file URL on disk. A synchronous image download is probably the reason why you've moved this into a separate thread, because it was blocking your UI right? One of the first things you should learn about network access it to never use a synchronous API on the main thread. Have you considered looking at the NSURLConnection class in order to do an asynchronous download?
Thirdly, this needs to be re-architected so that the image download isn't directly linked to the display of the cell.
Displaying the table cell should be done on the main thread with the rest of the UI. When the cell is displayed, it should make a call to download the image asynchronously. The image download should be cached, and the cell should be informed via a delegate callback when the image download is complete. It should then display the image.
You can wrap these kind of things up in NSOperations and use NSOperationQueue to manage them. Please, look at the documentation for these classes and checkout the example code for them to learn how to use them.

iphone asynchronous file load?

is there way to load local files in iphone asynchronously? I load uiimages for my uitableview using this:
NSData *imageData = [[NSData alloc] initWithContentsOfFile:fileName];
UIImage *cachedImage = [[[UIImage alloc] initWithData:imageData] autorelease];
but it is slow, because main thread is locked or something until NSData finishes loading the file and UI becomes unresponsive. Is there something like NSURLConnection but for local files? So I can just load file without freezing UI, and when it finishes loading, some handler sends notification or something like that.
You can use an NSOperationQueue and NSInvocationOperation to call a 'load' procedure. Then, from the load procedure, simply use the 'performSelectorOnMainThread' to update. See: http://gist.github.com/375559 for a detailed example.