Calvin
Calvin

Reputation: 331

sdwebimage with permanent caching

I am developing an iOS app that requires the SDWebImage to permanently cache pictures onto the phone.

  1. There is an expiration setting in SDWebImage codes, should I set the expiration time to a large value to store the cache permanently?
  2. Since I wanted the pictures to be cached permanently, should I stored them into a dedicated folder or the default directory is sufficient? My app needs those picture to be persistent when the app is closed and re-open as well as when the phone is restarted.
  3. If I wanted to cache the picture permanently, is there anything else I need to watch out other than setting the expiration to a large value?

Thanks.

Upvotes: 3

Views: 1435

Answers (1)

MolhamStein
MolhamStein

Reputation: 627

Unfortunately SDWebImage Does not provide such a capability so to make use of the advanced caching capabilities provided by SDWebImage i wrote a wrapper around it

basically this class manages a fallback permanent cache so if the requested image not found in the SDWeb image cache "disk and memory" it will look for it in the permanent cache and if it was found the permanent cache it will be copied to the SDwebImage cache to make use of its memory cashing in later requests

using this approach i managed to keep setting images to table cells as smooth as usual with SDWebImage you can also trigger clearImageCache to errase the permanent cache whenever you need

.h file

@interface CacheManager : NSObject
+ (CacheManager*)sharedManager;

// Images
- (BOOL) diskImageExistsForURL:(NSURL*) url;
- (void) downloadImage:(NSURL*) url completed:(void(^)(UIImage*))onComplete;
- (void) setImage:(NSURL*)url toImageView:(UIImageView*)iv completed:(void(^)(UIImage*))onComplete;
- (void) clearImageCache;

@end

.m file

#import "CacheManager.h"
#import <SDWebImage/UIImageView+WebCache.h>


#define CACH_IMAGES_FOLDER      @"ImagesData"


@implementation CacheManager


static CacheManager *sharedManager = nil;

#pragma mark -
#pragma mark Singilton Init Methods
// init shared Cache singleton.
+ (CacheManager*)sharedManager{
    @synchronized(self){
        if ( !sharedManager ){
            sharedManager = [[CacheManager alloc] init];
        }
    }
    return sharedManager;
}

// Dealloc shared API singleton.
+ (id)alloc{
    @synchronized( self ){
        NSAssert(sharedManager == nil, @"Attempted to allocate a second instance of a singleton.");
        return [super alloc];
    }
    return nil;
}

// Init the manager
- (id)init{
    if ( self = [super init] ){}
    return self;
}

/**
  @returns YES if image found in the permanent cache or the cache managed by SDWebImage lib
 */
- (BOOL) diskImageExistsForURL:(NSURL*) url{

    // look for image in the SDWebImage cache
    SDWebImageManager *manager = [SDWebImageManager sharedManager];
    if([manager diskImageExistsForURL:url])
        return YES;

    // look for image in the permanent cache
    NSString *stringPath = url.path;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    return [fileManager fileExistsAtPath:stringPath];
}

/**
 get the image with specified remote url asynchronosly 
 first looks for the image in SDWeb cache to make use of the disk and memory cache provided by SDWebImage
 if not found, looks for it in the permanent cache we are managing, finally if not found in either places
 it will download it using SDWebImage and cache it.
 */
- (void) downloadImage:(NSURL*) url completed:(void(^)(UIImage*))onComplete{

    NSString *localPath = [[self getLocalUrlForImageUrl:url] path];
    NSFileManager *fileManager = [NSFileManager defaultManager];

    // -1 look for image in SDWeb cache
    SDWebImageManager *manager = [SDWebImageManager sharedManager];
    if([manager diskImageExistsForURL:url]){
        [manager downloadImageWithURL:url options:SDWebImageRetryFailed
                             progress:^(NSInteger receivedSize, NSInteger expectedSize) {}
                            completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                                onComplete(image);

                                // save the image to the perminant cache for later
                                // if not saved before
                                if(image){
                                    if ([fileManager fileExistsAtPath:localPath]){
                                        NSURL* localeUrl = [self getLocalUrlForImageUrl:url];
                                        [self saveImage:image toCacheWithLocalPath:localeUrl];
                                    }
                                }
                            }];
        return;
    }

    // -2 look for the image in the permanent cache
    if ([fileManager fileExistsAtPath:localPath]){
        UIImage *img = [self getImageFromCache:url];
        onComplete(img);
        // save image back to the SDWeb image cache to make use of the memory cache
        // provided by SDWebImage in later requests
        [manager saveImageToCache:img forURL:url];
        return;
    }

    // -3 download the image using SDWebImage lib
    [manager downloadImageWithURL:url options:SDWebImageRetryFailed
                         progress:^(NSInteger receivedSize, NSInteger expectedSize) {}
                        completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                            onComplete(image);
                            // save the image to the permanent cache for later
                            if(image){
                                NSURL* localeUrl = [self getLocalUrlForImageUrl:url];
                                [self saveImage:image toCacheWithLocalPath:localeUrl];
                            }
                        }];

}

- (void) setImage:(NSURL*)url toImageView:(UIImageView*)iv completed:(void(^)(UIImage*))onComplete{
    [self downloadImage:url completed:^(UIImage * downloadedImage) {
        iv.image = downloadedImage;
        onComplete(downloadedImage);
    }];
}


/**
 @param:imgUrl : local url of image to read
 */
- (UIImage*) getImageFromCache:(NSURL*)imgUrl{
    return [UIImage imageWithData: [NSData dataWithContentsOfURL:imgUrl]];
}

/**
 writes the suplied image to the local path provided
 */
-(void) saveImage:(UIImage*)img toCacheWithLocalPath:(NSURL*)localPath{
    NSData * binaryImageData = UIImagePNGRepresentation(img);
    [binaryImageData writeToFile:[localPath path] atomically:YES];
}

// Generate local image URL baesd on the name of the remote image
// this assumes the remote images already has unique names
- (NSURL*)getLocalUrlForImageUrl:(NSURL*)imgUrl{
    // Saving an offline copy of the data.
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *cachesDirectory = [paths objectAtIndex:0];
    NSString *folderPath = [cachesDirectory stringByAppendingPathComponent:CACH_IMAGES_FOLDER];
    BOOL isDir;
    // create folder not exist
    if (![fileManager fileExistsAtPath:folderPath isDirectory:&isDir]){
        NSError *dirWriteError = nil;
        if (![fileManager createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:&dirWriteError]){
            NSLog(@"Error: failed to create folder!");
        }
    }

    NSString *imgName = [[[imgUrl path] lastPathComponent] stringByDeletingPathExtension];

    NSURL *cachesDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
    NSString *pathString = [NSString stringWithFormat:@"%@/%@", CACH_IMAGES_FOLDER, imgName];
    return [cachesDirectoryURL URLByAppendingPathComponent:pathString];
}


/**
 removes the folder contating the cahced images,
 the folder will be reacreated whenever a new image is being saved to the permanent cache
 */
-(void)clearImageCache{
    // set the directory path
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *cachesDirectory = [paths objectAtIndex:0];
    NSString *folderPath =  [cachesDirectory stringByAppendingPathComponent:CACH_IMAGES_FOLDER];
    BOOL isDir;
    NSError *dirError = nil;
    // folder exist
    if ([fileManager fileExistsAtPath:folderPath isDirectory:&isDir]){
        if (![fileManager removeItemAtPath:folderPath error:&dirError])
        NSLog(@"Failed to remove folder");
    }
}

@end

Upvotes: 6

Related Questions