Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save dstd/7cafb6c2484a0ad93cd9 to your computer and use it in GitHub Desktop.
Save dstd/7cafb6c2484a0ad93cd9 to your computer and use it in GitHub Desktop.
Progressive image loading for iOS. This is a GIST - detailed implementation is left as an exercise for the reader.
/*
typical usage (FYImageStore is a singleton)
[[FYImageStore instance] retrieveFullsizeImageFromPath:path
completion:^(UIImage* image){
self.imageView.image = image
}];
*/
#import "FYImageURL.h" //URL-building utilities, omitted from this Gist.
/*
path is the full path to the image resource, either in the local cache or on the server.
paths have varying suffixes for resource size
eg small_thumb.jpg / large_thumb.jpg
error checking omitted from this Gist.
*/
typedef void (^RetrieveImage)(UIImage *image);
typedef enum {
kSmallThumbImage,
kLargeThumbImage,
kFullsizeImage,
kNormalSize
} ImageSize;
@interface FYImageStore
@property (nonatomic, strong) NSOperationQueue* accessQueue;
@property (nonatomic, strong) NSCache* imageCache;
@property (nonatomic, strong) NSMutableDictionary* operations;
//progressive loading of lower-res images
- (void) retrieveFullsizeImageFromPath:(NSString*)path
completion:(RetrieveImage)completionBlock;
- (void)retrieveLargeThumbImageFromPath:(NSString*)path
completion:(RetrieveImage)completionBlock;
- (void)retrieveSmallThumbImageFromPath:(NSString*)path
completion:(RetrieveImage)completionBlock;
//specified res only, no progressive loading
- (void)retrieveImageFromPath:(NSString*)path
completion:(RetrieveImage)completionBlock;
//respond to low memory warnings
- (void)lowMemoryCleanup;
@end
@implementation FYImageStore //(initialisation and other details omitted from the gist)
#pragma mark - async requests from path
- (void) retrieveFullsizeImageFromPath:(NSString*)path
completion:(RetrieveImage)completionBlock
{
[self retrieveImageFromPath:path
ofSize:kFullsizeImage
completion:completionBlock];
}
- (void)retrieveLargeThumbImageFromPath:(NSString*)path
completion:(RetrieveImage)completionBlock
{
[self retrieveImageFromPath:path
ofSize:kLargeThumbImage
completion:completionBlock];
}
- (void)retrieveSmallThumbImageFromPath:(NSString*)path
completion:(RetrieveImage)completionBlock
{
[self retrieveImageFromPath:path
ofSize:kSmallThumbImage
completion:completionBlock];
}
- (void)retrieveImageFromPath:(NSString*)path
completion:(RetrieveImage)completionBlock {
[self retrieveImageFromPath:path
ofSize:kNormalSize
completion:completionBlock];
}
- (void)retrieveImageFromPath:(NSString*)path
ofSize:(ImageSize)size
completion:(RetrieveImage)completionBlock
{
//recursively retrieve lower-res images
if (path && (size>0 && size<3)) {
[self retrieveImageFromPath:path
ofSize:(size-1)
completion:completionBlock];
}
//set up block operation
NSBlockOperation* operation = [[NSBlockOperation alloc] init];
__weak typeof (operation) operation_ = operation;
[operation addExecutionBlock:^{
__strong typeof (operation) operation__ = operation_;
NSString* imagePath = [FYImageURL imagePath:path forSize:size];
if (operation__.isCancelled) return;
UIImage* image = [self imageFromPath:imagePath ofSize:size];
if (!image) return;
[self cancelOperationsForPath:path size:size];
if (operation__.isCancelled) {
image = nil;
return;
};
dispatch_async(dispatch_get_main_queue(), ^{
if (!operation__.isCancelled) {
completionBlock(image);
}
});
}];
//add operation to operations dictionary (so that we can cancel)
if ([[self.operations allKeys] containsObject:path]) {
NSMutableDictionary* dict = [self.operations objectForKey:path];
[dict setObject:operation forKey:@(size)];
} else {
NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
[dict setObject:operation forKey:@(size)];
[self.operations setObject:dict forKey:path];
}
//add operation to operation queue for execution
[self.accessQueue addOperation:operation];
}
//operation cancelling
//when we retrieve an image of higher res,
//cancel operations for same image of lower res
- (void)cancelOperationsForPath:(NSString*)path size:(ImageSize)size {
if (![self.operations objectForKey:path]) return;
for (int idx=0; idx<size; idx++) {
NSOperation* operation = [[self.operations objectForKey:path] objectForKey:@(idx)];
[operation cancel];
[[self.operations objectForKey:path] removeObjectForKey:@(idx)];
if ([[self.operations objectForKey:path] allKeys].count == 0) {
[self.operations removeObjectForKey:path];
}
}
}
- (UIImage*) imageFromPath:(NSString*)path
ofSize:(ImageSize)size
{
NSString* imagePath = [FYImageURL imagePath:path forSize:size];
return [self imageFromPath:imagePath];
}
- (UIImage*) imageFromPath:(NSString*)imagePath
{
//get from NSCache
UIImage* image = [self imageFromNSCache:imagePath];
if (!image) {
//if it's not in NSCache, get from the file cache
image = [self imageFromFileCache:imagePath];
}
if (!image) {
//if it's not in the file cache, fetch from the server
image = [self imageFromServer:imagePath];
//(this operation will also write to the file cache)
}
return image;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment