CNLiveImageCache.m 7.44 KB
//
//  CNLiveImageCache.m
//  WSWebImageDemo
//
//  Created by 王帅 on 16/7/19.
//  Copyright © 2016年 王帅. All rights reserved.
//

#import "CNLiveImageCache.h"
#import <CommonCrypto/CommonDigest.h>
#import "UIImage+CNLiveMultiFormat.h"

FOUNDATION_STATIC_INLINE NSUInteger CNLiveCacheCostForImage(UIImage *image) {
    return image.size.height * image.size.width * image.scale * image.scale;
}

typedef void(^CNLiveWebImageNoParamsBlock)();

@interface CNLiveImageCache ()

@property (nonatomic, strong) NSCache *memoryCache;
@property (nonatomic, copy)   NSString *diskCachePath;
@property (nonatomic, strong) dispatch_queue_t ioQueue;

@end

@implementation CNLiveImageCache
{
    NSFileManager *_fileManager;
}

+ (CNLiveImageCache *)sharedImageCache
{
    static dispatch_once_t onceToken;
    static id instance;
    dispatch_once(&onceToken, ^{
        instance = [self new];
    });
    return instance;
}

- (id)init {
    return [self initWithNamespace:@"default"];
}

- (id)initWithNamespace:(NSString *)ns {
    NSString *path = [self makeDiskCachePath:ns];
    return [self initWithNamespace:ns diskCacheDirectory:path];
}

- (NSString *)makeDiskCachePath:(NSString *)fullNamespace {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    return [paths[0] stringByAppendingString:fullNamespace];
}

- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory {
    if (self = [super init]) {
        NSString *fullNameSpace = [@"com.cnlive.CNLiveImageCache." stringByAppendingString:ns];
        _ioQueue = dispatch_queue_create("com.cnlive.CNLiveImageCacheQueue", DISPATCH_QUEUE_SERIAL);
        _memoryCache = [[NSCache alloc] init];
        _memoryCache.name = fullNameSpace;
        
        if (directory != nil) {
            _diskCachePath = [directory stringByAppendingPathComponent:fullNameSpace];
        }
        else {
            _diskCachePath = [[self makeDiskCachePath:ns] stringByAppendingPathComponent:fullNameSpace];
        }
        
        dispatch_sync(_ioQueue, ^{
            _fileManager = [NSFileManager new];
        });
        
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(clearMemory)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];
        
//        [[NSNotificationCenter defaultCenter] addObserver:self
//                                                 selector:@selector(clearDisk)
//                                                     name:UIApplicationWillTerminateNotification
//                                                   object:nil];
    }
    return self;
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk
{
    if (!image || !key) {
        return;
    }
    
    NSUInteger cost = CNLiveCacheCostForImage(image);
    [_memoryCache setObject:image forKey:key cost:cost];
    
    if (toDisk) {
        dispatch_async(_ioQueue, ^{
            NSData *data = UIImageJPEGRepresentation(image, 1.0);
            
            if (data) {
                if (![_fileManager fileExistsAtPath:_diskCachePath]) {
                    [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
                }
                NSString *cachePathForKey = [self defaultCachePathForKey:key];
                [_fileManager createFileAtPath:cachePathForKey contents:data attributes:nil];
            }
        });
    }
}

- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key
{
    return [self.memoryCache objectForKey:key];
}

- (UIImage *)imageFromDiskCacheForKey:(NSString *)key
{
    UIImage *memoryImage = [self imageFromMemoryCacheForKey:key];
    if (memoryImage) {
        return memoryImage;
    }
    
    NSData *diskData = [self getDiskImageDataWithKey:key];
    if (diskData) {
        return [UIImage ws_imageWithData:diskData];
    }
    
    return nil;
}

- (BOOL)queryDiskCacheForKey:(NSString *)key done:(CNLiveWebImageQueryCompletedBlock)doneBlock
{
    if (!doneBlock) {
        return NO;
    }
    if (!key) {
        doneBlock(nil, CNLiveImageCacheTypeNone);
        return NO;
    }
    
    UIImage *memoryImage = [self imageFromMemoryCacheForKey:key];
    if (memoryImage) {
        doneBlock(memoryImage, CNLiveImageCacheTypeMemory);
        return YES;
    }
    
    UIImage *diskImage = [self imageFromDiskCacheForKey:key];
    if (diskImage) {
        doneBlock(diskImage, CNLiveImageCacheTypeDisk);
        return YES;
    }
    
    doneBlock(nil, CNLiveImageCacheTypeNone);
    return NO;
    
}

- (void)removeCacheForKey:(NSString *)key done:(CNLiveCompletedBlock)completed
{
    if (!key || !completed) {
        return;
    }
    
    if ([self imageFromMemoryCacheForKey:key]) {
        [self.memoryCache removeObjectForKey:key];
    }
    
    if ([self imageFromDiskCacheForKey:key]) {
        NSString *cachePathForKey = [self defaultCachePathForKey:key];
        [_fileManager removeItemAtPath:cachePathForKey error:nil];
    }
    
    completed();
}

- (long long)diskCacheSize
{
    if ([_fileManager fileExistsAtPath:self.diskCachePath]) {
        NSArray *subPathArray = [_fileManager subpathsAtPath:self.diskCachePath];
        if (subPathArray && subPathArray.count > 0) {
            long long totalCacheSize = 0;
            for (NSString *subPath in subPathArray) {
                NSString *fullPath = [self.diskCachePath stringByAppendingPathComponent:subPath];
                NSDictionary *cacheSizeDic = [_fileManager attributesOfItemAtPath:fullPath error:nil];
                totalCacheSize += [cacheSizeDic fileSize];
            }
            return totalCacheSize;
        }
    }
    return 0;
}

- (void)clearMemory
{
    [self.memoryCache removeAllObjects];
}

- (void)clearDisk
{
    [self clearDiskOnCompletion:nil];
}

- (void)clearDiskOnCompletion:(CNLiveWebImageNoParamsBlock)completion
{
    dispatch_async(self.ioQueue, ^{
        [_fileManager removeItemAtPath:self.diskCachePath error:nil];
        [_fileManager createDirectoryAtPath:self.diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
        
        if (completion) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completion();
            });
        }
    });
}

#pragma mark <PrivateMethod>
- (NSData *)getDiskImageDataWithKey:(NSString *)key
{
    NSString *filePath = [self defaultCachePathForKey:key];
    NSData *imageData = [NSData dataWithContentsOfFile:filePath];
    return imageData;
}

- (NSString *)defaultCachePathForKey:(NSString *)key
{
    return [self cachePathForKey:key inPath:self.diskCachePath];
}

- (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path
{
    NSString *fileName = [self cachedFileNameForKey:key];
    return [path stringByAppendingPathComponent:fileName];
}

- (NSString *)cachedFileNameForKey:(NSString *)key {
    const char *str = [key UTF8String];
    if (str == NULL) {
        str = "";
    }
    unsigned char r[CC_MD5_DIGEST_LENGTH];
    CC_MD5(str, (CC_LONG)strlen(str), r);
    NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
                          r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15]];
    
    return filename;
}

@end