views:

441

answers:

3

I'm making an app that allows you to browse through pictures from a website. I'm currently downloading the images using:

 UIImage *myImage = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url]]];

which works great, but can be time consuming. I start off by downloading 20 images, but I can't do anything until after the 30 or so seconds it takes to download them all.

This one time wait isn't all that bad, but if I want to download the 21st-40th images, I would have to wait another 30 seconds.

Basically, is there a way I can download these images one at a time without holding up any of my animations?

Thanks.

+7  A: 

Sure, put the download task in a thread, and use a callback to let your program know when each image is finished. Then you can draw your images as they finish loading, and not hold up the rest of the app. This link has a template that you can use as an example.

Here's a quick and dirty example:

- (void)downloadWorker:(NSString *)urlString
{
    NSAutoreleasePool *pool  = [[NSAutoreleasePool alloc] init];
    NSURL             *url   = [NSURL URLWithString:urlString];
    NSData            *data  = [NSData dataWithContentsOfURL:url];
    UIImage           *image = [[UIImage alloc] initWithData:data];

    [self performSelectorOnMainThread:@selector(imageLoaded:) 
                           withObject:image
                        waitUntilDone:YES];
    [image release];
    [pool drain];
}

- (void)downloadImageOnThread:(NSString *)url
{
    [NSThread detachNewThreadSelector:@selector(downloadWorker:) 
                             toTarget:self 
                           withObject:url];
}

- (void)imageLoaded:(UIImage *)image
{
    // get the image into the UI
}

Call downloadImageOnThread for every image you want to load, each will get its own thread, and you'll get calls to imageLoaded as each one completes.

Carl Norum
I'm sorry, but I'm not really sure what you mean or how I can do it. Could you explain a little further?
pureman
@pureman, did you see the link? It's pretty straightforward - you use `[NSThread detachNewThreadSelector...]` to create a thread with a specific method, and then use `performSelectorOnMainThread` to call back to the app when the worker thread is done.
Carl Norum
I'll add some code to my answer to clear it up a bit.
Carl Norum
+1 Nicely written.
Rob Napier
Thanks a bunch. The code you provided worked great and I'm starting to grasp the concept. Many thanks, once again.
pureman
+1  A: 

yeah you can use a secondary thread, and do a lot of work OR you could use things that apple gives us.

NSURLDownload, doesn't "lag" your main thread, You spawn it with a method and you set a endSelector, the endSelector will get called when the download is done. Spawning a secondary thread for this is not really what you should do.

here you got some code from my app wich does it work perfectly without giving a beach ball of doom.

- (void)downloadAvatar:(NSString *)URL{
 NSURL *url = [[NSURL alloc] initWithString:URL];
 NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
 [url release];
 NSURLDownload *download = [[NSURLDownload alloc] initWithRequest:request delegate:self];
 NSString *path = [[NSString alloc] initWithFormat:@"%@data/%@.jpg",[[BFAppSupport defaultSupport] bfFolderPath],[[xfSession loginIdentity] userName]];
 [download setDestination:path allowOverwrite:YES];
 [download release];
 [path release];
 [request release];
}
- (void)downloadDidFinish:(NSURLDownload *)download{
 NSString *path = [[NSString alloc] initWithFormat:@"%@data/%@.jpg",[[BFAppSupport   defaultSupport] bfFolderPath],[[xfSession loginIdentity] userName]];
 NSData *imageData = [[NSData alloc] initWithContentsOfFile:path];
if( [imageData length] < 10 ){
  [self performSelector:@selector(downloadAvatar:) withObject:@"http://media.xfire.com/xfire/xf/images/avatars/gallery/default/xfire160.jpg" afterDelay:0.0];
  [imageData release];
  [path release];
  return;
}
NSImage *theImage = [[NSImage alloc] initWithData:imageData];
[imageData release];
[path release];
[yourImage setImage:theImage];
[theImage release];
}

- (void)download:(NSURLDownload *)aDownload didFailWithError:(NSError *)error{
 NSLog(@"Avatar url download failed");
}

The code is a bit ugly, but its not hard to change it as you got the 3 things you need, the method that starts the download and 2 that handle or an error or the finish. You can also use autoreleased objects some more, but in terms of performance I like using it without autoreleased objects when I can.

Antwan van Houdt
+3  A: 

Hi,

While loading the image on a background thread is definately the solution, I'd use an NSOperation and an NSOperationQueue instead of dealing with the threads yourself (this is the way Apple recommend to deal with threading problems like this!)

The NSOperationQueue will deal with starting/stopping the threads nicely and you can choose how many to run at once etc. It's basically a the same as the other answers but you get a little more control.

There's a tutorial here that looks pretty good.

deanWombourne