Best way to cache images on ios app?

Solution 1:

Caching just means keeping a copy of the data that you need so that you don't have to load it from some slower source. For example, microprocessors often have cache memory where they keep copies of data so that they don't have to access RAM, which is a lot slower. Hard disks often have memory caches from which the file system can get much quicker access to blocks of data that have been accessed recently.

Similarly, if your app loads a lot of images from the network, it may be in your interest to cache them on your device instead of downloading them every time you need them. There are lots of ways to do that -- it sounds like you already found one. You might want to store the images you download in your app's /Library/Caches directory, especially if you don't expect them to change. Loading the images from secondary storage will be much, much quicker than loading them over the network.

You might also be interested in the little-known NSCache class for keeping the images you need in memory. NSCache works like a dictionary, but when memory gets tight it'll start releasing some of its contents. You can check the cache for a given image first, and if you don't find it there you can then look in your caches directory, and if you don't find it there you can download it. None of this will speed up image loading on your app the first time you run it, but once your app has downloaded most of what it needs it'll be much more responsive.

Solution 2:

I think Caleb answered the caching question well. I was just going to touch upon the process for updating your UI as you retrieve images, e.g. assuming you have a NSCache for your images called _imageCache:

First, define an operation queue property for the tableview:

@property (nonatomic, strong) NSOperationQueue *queue;

Then in viewDidLoad, initialize this:

self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 4;

And then in cellForRowAtIndexPath, you could then:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ilvcCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    // set the various cell properties

    // now update the cell image

    NSString *imagename = [self imageFilename:indexPath]; // the name of the image being retrieved

    UIImage *image = [_imageCache objectForKey:imagename];

    if (image)
    {
        // if we have an cachedImage sitting in memory already, then use it

        cell.imageView.image = image;
    }
    else
    {
        cell.imageView.image = [UIImage imageNamed:@"blank_cell_image.png"];

        // the get the image in the background

        [self.queue addOperationWithBlock:^{

            // get the UIImage

            UIImage *image = [self getImage:imagename];

            // if we found it, then update UI

            if (image)
            {
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    // if the cell is visible, then set the image

                    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
                    if (cell)
                        cell.imageView.image = image;
                }];

                [_imageCache setObject:image forKey:imagename];
            }
        }];
    }

    return cell;
}

I only mention this as I've seen a few code samples floating around on SO recently that use GCD to update the appropriate UIImageView image property, but in the process of dispatching the UI update back to the main queue, they employ curious techniques (e.g., reloading the cell or table, just updating the image property of the existing cell object returned at the top of the tableView:cellForRowAtIndexPath (which is a problem if the row has scrolled off the screen and the cell has been dequeued and is being reused for a new row), etc.). By using cellForRowAtIndexPath (not to be confused with tableView:cellForRowAtIndexPath), you can determine if the cell is still visible and/or if it may have scrolled off and been dequeued and reused.

Solution 3:

The simplest solution is to go with something heavily used that has been stress tested.

SDWebImage is a powerful tool that helped me solve a similar problem and can easily be installed w/ cocoa pods. In podfile:

platform :ios, '6.1'
pod 'SDWebImage', '~>3.6'

Setup cache:

SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@"myNamespace"];
[imageCache queryDiskCacheForKey:myCacheKey done:^(UIImage *image)
{
    // image is not nil if image was found
}];

Cache image:

[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];

https://github.com/rs/SDWebImage