iphone asynchronous file load? - iphone

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.

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.

iPhone - GCD doesn't work for the first time

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.

How to download things without display hanging?

Okay. So I'm creating this app and I'm really happy with it apart from one thing. It uses a lot of internet database checking etc. and every time it does this it hangs until it has finished, spoiling the fluidity of the interface. Is there anyway to avoid this? Download stuff happens when you click a button or sometimes just as a result of an NSTimer. I would love for the user to be able to go through the interface fluidly and the app to update the info when it gets there. IS there anyway to do this?
EDIT - Current Synchronous code:
This is to download a CSV file:
NSString *link=[[NSString alloc] initWithFormat:#"http://d.yimg.com/autoc.finance.yahoo.com/autoc?query=%#&callback=YAHOO.Finance.SymbolSuggest.ssCallback",timer.userInfo];
NSURL *url=[[NSURL alloc] initWithString:link];
NSData *dl=[[NSData alloc] initWithContentsOfURL:url];
NSString *str = [[NSString alloc] initWithData:dl encoding:NSUTF8StringEncoding];
This is to download a png file into a webView:
NSString *link=[[NSString alloc] initWithFormat:#"http://chart.finance.yahoo.com/z?s=%#&t=GOOG&q=m",self.Input.text];
url=[[NSURL alloc] initWithString:link];
[self.graph loadRequest:[NSURLRequest requestWithURL:url]];
It sounds like you're making synchronous requests. You shouldn't be doing that on the main thread. Your best option is probably to switch to asynchronous requests. You could make synchronous requests on a background thread, but that's more complicated and unnecessary in most situations.

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.