Lance
Lance

Reputation: 9012

Image cacheing in thread for UITableViewCell

I have an image caching class that stores profile images in an NSMutableDictionary. If the key exists, the cache returns a UIImage object, if it does not, it downloads it, stores it in the dictionary, and returns the UIImage. This shared cache is called inside a NSThreaded method like this:

[NSThread detachNewThreadSelector:@selector(setImageForUser:) toTarget:self withObject:imageUrlString];

-(void)setImageForUser:(NSString *)imageUrlString {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    LPImageCacher *imageCache = [LPImageCacher sharedCache];
    UIImage *profileImg = [imageCache getCachedImageFromMemory:imageUrlString];
    self.userProfileImageView.image = profileImg;
    if (profileImg == nil) {
        imageUrlString = [[myGlobals sharedInstance].avatarUrlDefault stringByAppendingString:@"/avatar.png"];
        self.userProfileImageView.image = [imageCache getCachedImageFromMemory:imageUrlString];
    }

    [pool release];
}

The problem is that if they scroll the UITableView before anything is loaded, the images get backlogged and then eventually all the images are loaded and they flash and change on the current cells until it gets to the correct ones. I thought it would be good to create an instance of NSThread in each cell and just cancel the thread in -prepareForReuse but I can't seem to be able to reuse the NSThread object again, any ideas on how to fix this?

Upvotes: 1

Views: 549

Answers (3)

UPT
UPT

Reputation: 1480

You should use the main thread to update the screen to user. This Common multithreading mistakes beginners make on iPhone will help you to determine the problem.

Upvotes: 0

Can
Can

Reputation: 8571

Reed Olsen gave you the direct answer. I want to mention another thing.

Dispatching threads for each cell is rather costly, why not use a NSOperationQueue? You can have a set of worker threads doing each individual load, and it would make sense to have this queue limited in function to the maximum amount of visible cells at any time. This is pretty much "reusing threads".

In fact, I would suggest using ASIHTTP's ASINetworkQueue. It's even simpler to use than NSOperationQueue.

Also, try adding a simple optimization to load the cells once the scroll speed is below a certain number. Why bother loading images when you won't be able to stop to see them? For instance, I wrote something like this for a tableView:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    static float prevOffset = 0.0;
    static float scrollSpeed = 0.0;

    // positive -> downwards
    scrollSpeed = scrollView.contentOffset.y - prevOffset;

    if (scrollSpeed < 2.0)
    {
        [self loadImagesForVisibleCellsInTableView:(UITableView*)scrollView];
    }
    else if (scrollSpeed < 6.0)
    {
        shouldLoadImages = YES;
    }
    else
    {
        shouldLoadImages = NO;
    }

    prevOffset = scrollView.contentOffset.y;
}

This ignores loading new images when the tableView is moving fast (shouldLoadImages affects tableView:cellForRowAtIndexPath, telling them to ignore loading images) and when it's about to stop, it forces to reload the visible cells.

Upvotes: 0

Reed Olsen
Reed Olsen

Reputation: 9169

I think the problem that you're seeing is due to the fact that UIScrollView "freezes" everything whenever you're scrolling. This is a run loop issue. In order to get around this, you need to make sure that you're sending your -setImageForUser: message on the default run loop.

Check these resources for more information:

NSURLRequest won't fire while UIScrollView is scrolling

UIScrollView pauses NSTimer until scrolling finishes

Hope this helps!

Upvotes: 1

Related Questions