Skip to content
This repository has been archived by the owner on May 3, 2021. It is now read-only.

Image Preheating Guide

Alexander Grebenyuk edited this page Sep 18, 2015 · 10 revisions

What is Preheating

Preheating is one of the most effective ways to improve user experience in applications that display collections of images. Preheating is a way to anticipate user actions and load images that might soon appear on the display. Loaded images are stored in persistent and/or memory caches.

Efficient image preheating implementation should:

  1. Determine which images to preheat and when to stop preheating them
  2. Guarantee that preheat requests do not interfere with normal (non-preheating) requests
  3. Use a single load tasks for multiple equivalent requests
  4. Implement caching properly
  5. Carefully manage system resources

Fortunately, DFImageManager provides everything to do just that.

Preheating and Collection Views

The most common way to display collections of images in iOS is a UICollectionView with a UICollectionViewFlowLayout. The first step is to determine which images in a collection view to preheat and when to stop preheating them. That's where DFCollectionViewPreheatController comes into play.

Collection view has a viewport (portion of the content that is visible on the screen) and two scroll directions (vertical and horizontal). In order to determine which images to preheat the DFCollectionViewPreheatController calculates a preheat window (CGRect) which is marginally larger than the viewport. It then asks collection view layout for the index paths of items in the preheat window.

Preheat window gets updated when the user scrolls the content. Its only necessary to update preheat window if the change in content offset is significant. Whenever preheat window changes the preheat controller tells its delegate about the change and provides two arrays of index paths (for items that were added and removed from preheat window). The delegate is supposed to start preheating images for items at added index paths and stop preheating images for removed items.

Preheating in DFImageManager

<DFImageManaging> protocol provides a set of self-explanatory methods for image preheating:

- (void)startPreheatingImagesForRequests:(NSArray /* DFImageRequest */ *)requests;
- (void)stopPreheatingImagesForRequests:(NSArray /* DFImageRequest */ *)requests;
- (void)stopPreheatingImageForAllRequests;

When the application starts preheating requests the image manager begins to fetch images, which are then processed and stored in caches. At any time during preheating and afterwards, the application can call -requestImageForRequest:completion: and retrieve the image as soon as it is becomes available. The application is responsible to provide the same requests when preheating and when actually requesting the images or else the preheating might be either partially effective or not effective at all.

DFImageManager does its best to guarantee that preheating requests never interfere with normal (non-preheating) requests. Preheating requests don't start executing until there are any non-preheating requests remaining. There is also a limit of concurrent preheating requests (while number of non-preheating concurrent requests is limited only by the <DFURLFetcher> implementation).

DFImageManager incorporates a lot of other features that make preheating better. For instance, there is a certain (very small) delay when the manager runs out of non-preheating requests and starts executing preheating requests. Given that fact, clients don't need to worry about the order in which they start their requests (preheating or not), which comes really handy when you, for example, reload collection view's data and start preheating and requesting multiple images at the same time.

Cache implementation is also crucial for preheating. DFImageManager does its best to implement image caching properly. For more info about caching in DFImageManager read Image Caching Guide.

Preheating Example

Here is an example of how you might implement preheating in you application using DFImageManager and DFCollectionViewPreheatingController (which can be used separately as well). There is more to preheating that's described in this guide so make sure to read the documentation.

@interface YourViewController () <DFCollectionViewPreheatingControllerDelegate>

@end

@implementation YourViewController {
    DFCollectionViewPreheatingController *_preheatingController;
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    
    _preheatingController = [[DFCollectionViewPreheatingController alloc] initWithCollectionView:self.collectionView];
    _preheatingController.delegate = self;
    [_preheatingController updatePreheatRect];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    
    // Preheat controller will tell delegate that all items were removed from preheat window
    [_preheatingController resetPreheatRect];
    _preheatingController = nil;
}

#pragma mark - <DFCollectionViewPreheatingControllerDelegate>

- (void)collectionViewPreheatingController:(DFCollectionViewPreheatingController *)controller 
        didUpdatePreheatRectWithAddedIndexPaths:(NSArray *)addedIndexPaths 
        removedIndexPaths:(NSArray *)removedIndexPaths {
    // All you need to do is to make sure that use the same requests for currently displayed cells,
    // or else preheating might not be effective
    NSArray *requestsForAddedItems = [self _imageRequestsForItemsAtIndexPaths:addedIndexPaths];
    [[DFImageManager sharedManager] startPreheatingImagesForRequests:requestsForAddedItems];
    
    NSArray *requestsForRemovedItems = [self _imageRequestsForItemsAtIndexPaths:removedIndexPaths];
    [[DFImageManager sharedManager] stopPreheatingImagesForRequests:requestsForRemovedItems];
}

- (NSArray /* DFImageRequest */ *)_imageRequestsForItemsAtIndexPaths:(NSArray *)indexPaths {
    return @[ /* Your requests */ ];
}