0

I was trying to load and cache images in my app for showing on UITableView. The original concepts looks like:

if (image in cache){
    show cached image;
}else{
   if (image in file){
     show filed image;
   }else{
      load image from web asynchronously;
      if (finished){
         store in cache;
         store in file;
      }
   }  
}

Here's the whole class I created to handle image storage:

ImageStore.h

@interface ImageStore : NSObject

@property (nonatomic, strong) NSCache *imageCache;

+ (ImageStore*)sharedImageStore;
//cache
- (void)writeImage: (UIImage*)image ToCacheForKey: (NSString*)imageURL;
- (UIImage*)readImageFromCacheForKey: (NSString*)imageURL;
- (BOOL)imageCachedForKey: (NSString*)imageURL;
//file
- (BOOL)writeImage: (UIImage*)image ToPlistFileForKey: (NSString*)imageURL;
- (UIImage*)readImageFromPlistFileForKey: (NSString*)imageURL;
- (BOOL)imageFiledInPlistForKey: (NSString*)imageURL;
@end

ImageStore.m

#import "ImageStore.h"

@implementation ImageStore
static ImageStore* sharedImageStore;
NSMutableDictionary* plistDictioary;

+(ImageStore*)sharedImageStore
{
    if (sharedImageStore == nil){
        sharedImageStore= [[self alloc] init];
    }
    return sharedImageStore;

}

+(id)alloc
{
    {
        NSAssert(sharedImageStore == nil, @"second singleton");
        sharedImageStore = [super alloc];

        return sharedImageStore;
    }

    return nil;
}

-(id)init
{
    self = [super init];
    if (self != nil)
    {
        _imageCache = [[NSCache alloc] init];
    }

    return self;
}

#pragma mark - Cache
- (void)writeImage:(UIImage *)image ToCacheForKey:(NSString *)imageURL{

    if(image != nil){
        [_imageCache setObject:image forKey:imageURL];
    }else{
        NSLog(@"NIL image:%@ ",imageURL);
    }
}

- (UIImage *)readImageFromCacheForKey:(NSString *)imageURL{

    return [_imageCache objectForKey:imageURL];
}

- (BOOL)imageCachedForKey:(NSString *)imageURL{

    if ([_imageCache objectForKey:imageURL] == nil)
    {
        return false;
    }

    return true;

}

#pragma mark - File
- (BOOL)writeImage:(UIImage*)image ToPlistFileForKey: (NSString*)imageURL{

    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:image];
    if (plistDictioary == nil) {
        plistDictioary = [NSMutableDictionary new];
    }
    [plistDictioary setObject:data forKey:imageURL];

    BOOL didWriteSuccessfull = [plistDictioary writeToFile:[self getPlistFilePath] atomically:YES];
    return didWriteSuccessfull;

}

- (UIImage *)readImageFromPlistFileForKey:(NSString *)imageURL{

    NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:[self getPlistFilePath]];
    NSData * data = [dict objectForKey:imageURL];
    UIImage * image = [UIImage imageWithData:data];
    return image;

}

- (BOOL)imageFiledInPlistForKey:(NSString *)imageURL{

    NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:[self getPlistFilePath]];
    if ([dict objectForKey:imageURL] == nil) {

        return NO;
    }else{

        return YES;
    }

}

- (NSString*)getPlistFilePath{

    NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString * documentsDirectory = [paths objectAtIndex:0];
    NSString * path = [documentsDirectory stringByAppendingPathComponent:@"Images.plist"];
    return path;
}
@end

Implementation in tableView:cellForRowAtIndex:

if ([[ImageStore sharedImageStore]imageCachedForKey:[[dataArray objectAtIndex:indexPath.row] objectForKey:@"image"]]) {
  //cache
  NSLog(@"cached");

  cell.image = [[ImageStore sharedImageStore] readImageFromCacheForKey:[[dataArray objectAtIndex:indexPath.row] objectForKey:@"image"]];

}else{

  NSLog(@"nothing cached");

  if ([[ImageStore sharedImageStore] imageFiledInPlistForKey:[[dataArray objectAtIndex:indexPath.row] objectForKey:@"image"]]) {
     //plist file

     cell.image = [[ImageStore sharedImageStore] readImageFromPlistFileForKey:[[dataArray objectAtIndex:indexPath.row] objectForKey:@"image"]];
  }else{
     //loading with UIImage category async method (see below)

     [UIImage loadFromURL:[NSURL URLWithString:[[dataArray objectAtIndex:indexPath.row] objectForKey:@"image"]] completionBlock:^(BOOL succeeded, UIImage *image) {

            if (succeeded){
                cell.image = image;
            }
        }];
    }
}

EDIT

Downloaded Images stored in the UIImage Category:

+ (void)loadFromURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
                               if ( !error )
                               {
                                   UIImage *image = [[UIImage alloc] initWithData:data];
                                   [[ImageStore sharedImageStore] writeImage:image ToCacheForKey:[url absoluteString]];//write to NSCache
                                   [[ImageStore sharedImageStore] writeImage:image ToPlistFileForKey:[url absoluteString]];//write to Plist
                                   completionBlock(YES,image);

                               } else{

                                   completionBlock(NO,nil);
                               }
                           }];

}

(Sorry for the long method naming.)

The console returns "nothing cached" for every onscreen cell, and sometimes the scrolling become stutter when checking cache and file.

I've been trying handy caching for a couple of days while there's already a bunch of useful image caching library. It'd be nice to know what am I doing wrong with this! Thanks for any advice.

bluenowhere
  • 2,683
  • 5
  • 24
  • 37

1 Answers1

0

The problem is in the writeImage:ToPlistFileForKey: method. You convert an image to a data as below:

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:image];

But convert the data to image like this:

UIImage * image = [UIImage imageWithData:data];

This will always return a nil that because the format of the archived data is NSPropertyListBinaryFormat_v1_0 not PNG(or bitmap format).

Instead of using [NSKeyedArchiver archivedDataWithRootObject:image], you should use UIImagePNGRepresentation(image) as below:

- (BOOL)writeImage:(UIImage*)image ToPlistFileForKey: (NSString*)imageURL {
    archivedDataWithRootObject:image];
    NSData *data = UIImagePNGRepresentation(image);
    if (plistDictioary == nil) {
        plistDictioary = [NSMutableDictionary new];
    }
    [plistDictioary setObject:data forKey:imageURL];

    BOOL didWriteSuccessfull = [plistDictioary writeToFile:[self getPlistFilePath] atomically:YES];
    return didWriteSuccessfull;
}

The UIImagePNGRepresentation function which returns the data for the specified image in PNG format, then you get an image [UIImage imageWithData:data].

Hope this will help you.

Bannings
  • 10,376
  • 7
  • 44
  • 54
  • thanks! that did the trick ! but the scrolling gets stuck when images are loaded and written into file at the first time, only if there's cache does the scroll feel smooth. Is that because of lack of image compression? – bluenowhere Jun 04 '15 at 05:27
  • That will be Optimized using `GCD` to writing and reading if your image is large, then you should no longer care for the return value of the `writeImage` method. – Bannings Jun 04 '15 at 05:46