YHURLCache.m 10.7 KB
//
//  YHURLCache.m
//  YohoExplorerDemo
//
//  Created by gaoqiang xu on 4/16/15.
//  Copyright (c) 2015 gaoqiang xu. All rights reserved.
//

#import "YHURLCache.h"
#import <CommonCrypto/CommonDigest.h>

@interface YHURLCache ()
@property (copy, atomic) NSString *diskPath;
@property (atomic) NSInteger cacheDuration;
@property (strong, atomic) NSCache *memCache;

@property (strong, nonatomic) NSArray *cacheTypes;

@property (strong, nonatomic) NSMutableDictionary *webViewCacheUrls;

@end

@implementation YHURLCache

#pragma mark - Override
- (void)dealloc
{
    [self.memCache removeAllObjects];
}

- (NSArray *)cacheTypes
{
    if (!_cacheTypes) {
        _cacheTypes = @[ @"jpg", @"jpeg", @"png", @"gif", @"html", @"css" ].mutableCopy;
    }
    return _cacheTypes;
}

- (instancetype)initWithMemoryCapacity:(NSUInteger)memoryCapacity
                          diskCapacity:(NSUInteger)diskCapacity
                              diskPath:(NSString *)path
                         cacheDuration:(NSInteger)cacheDuration
{
    self = [self initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:path];
    if (self) {
        _cacheDuration = cacheDuration;
    }
    
    return self;
}

- (instancetype)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)path
{
    self = [super initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:path];
    if (self) {
        if (path) {
            _diskPath = path;
        } else {
            _diskPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        }
        
        _memCache = [[NSCache alloc] init];
        _cacheDuration = 0;
    }
    
    return self;
}

- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
{
    if (request.cachePolicy == NSURLCacheStorageNotAllowed || [request.URL.absoluteString hasPrefix:@"file://"] || [request.URL.absoluteString hasPrefix:@"data:"]) {
        return nil;
    }
    
    NSString *url = [self removeParams:request.URL.absoluteString];
    
    NSString *extension = [[url pathExtension] lowercaseString];
    
    NSArray *types = [self.cacheTypes filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF = %@", extension]];
    
    if ([request.HTTPMethod isEqualToString:@"GET"]
        && (types.count == 1 || [self existInCacheDictionary:request] )) {

        return [self cachedDataForRequest:request];
    } else {
        return [super cachedResponseForRequest:request];
    }
}

- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request
{
    NSString *url = [self removeParams:request.URL.absoluteString];
    NSString *extension = [[url pathExtension] lowercaseString];
    
    NSArray *types = [self.cacheTypes filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF = %@", extension]];
    
    if ([request.HTTPMethod isEqualToString:@"GET"]
        && (types.count == 1 || [self existInCacheDictionary:request] )) {
        
        NSHTTPURLResponse *response = (NSHTTPURLResponse *)cachedResponse.response;
        if (cachedResponse.response && cachedResponse.data && [response statusCode] == 200) {
            
            NSDate *date = [NSDate date];
            NSString *url = request.URL.absoluteString;
            NSString *urlWithOtherInfo = [[NSString alloc] initWithFormat:@"%@-otherInfo", url];
            NSString *filePath = [self cacheFilePath:[self md5Hash:url]];
            NSString *otherInfoPath = [self cacheFilePath:[self md5Hash:urlWithOtherInfo]];
            
            NSDictionary *dic;
            if (cachedResponse.response.textEncodingName) {
                dic = @{ @"time":[NSString stringWithFormat:@"%.0f", [date timeIntervalSince1970]],
                         @"MIMEType":cachedResponse.response.MIMEType,
                         @"textEncodingName":cachedResponse.response.textEncodingName };
            } else {
                dic = @{ @"time":[NSString stringWithFormat:@"%.0f", [date timeIntervalSince1970]],
                         @"MIMEType":cachedResponse.response.MIMEType };
            }
            
            [self.memCache setObject:cachedResponse.data forKey:[filePath lastPathComponent]];
            [self.memCache setObject:dic forKey:[otherInfoPath lastPathComponent]];
            
            [dic writeToFile:otherInfoPath atomically:YES];
            [cachedResponse.data writeToFile:filePath atomically:YES];
        }
    } else {
        [super storeCachedResponse:cachedResponse forRequest:request];
    }
}

- (void)removeAllCachedResponses
{
    [super removeAllCachedResponses];
    
    [self deleteCacheFolder];
    
    [self.memCache removeAllObjects];
}

- (void)removeCachedResponseForRequest:(NSURLRequest *)request
{
    [super removeCachedResponseForRequest:request];
    
    NSString *url = request.URL.absoluteString;
    NSString *urlWithOtherInfo = [[NSString alloc] initWithFormat:@"%@-otherInfo", url];
    NSString *filePath = [self cacheFilePath:[self md5Hash:url]];
    NSString *otherInfoPath = [self cacheFilePath:[self md5Hash:urlWithOtherInfo]];
    
    [self.memCache removeObjectForKey:[filePath lastPathComponent]];
    [self.memCache removeObjectForKey:[otherInfoPath lastPathComponent]];
    
    NSFileManager *fm = [[NSFileManager alloc] init];
    [fm removeItemAtPath:filePath error:nil];
    [fm removeItemAtPath:otherInfoPath error:nil];
}

#pragma mark - Setter & Getter
- (NSMutableDictionary *)webViewCacheUrls
{
    if (!_webViewCacheUrls) {
        _webViewCacheUrls = @{}.mutableCopy;
    }
    return _webViewCacheUrls;
}

#pragma mark - Private
- (NSString *)removeParams:(NSString *)url
{
    NSString *freshUrl = url;
    
    NSRange rangeQ = [url rangeOfString:@"?"];
    if (rangeQ.location != NSNotFound) {
        freshUrl = [url substringToIndex:rangeQ.location];
    }
    
    return freshUrl;
}

- (BOOL)existInCacheDictionary:(NSURLRequest *)request
{
    BOOL existCacheUrl = NO;
    for (id object in [self.webViewCacheUrls allKeys]) {
        NSArray *temp = self.webViewCacheUrls[object];
        for (NSString *url in temp) {
            if ([url isEqualToString:request.URL.absoluteString]) {
                existCacheUrl = YES;
                break;
            }
        }
        
        if (existCacheUrl) {
            break;
        }
    }
    
    return existCacheUrl;
}

- (NSString *)md5Hash:(NSString *)str
{
    const char *cStr = [str UTF8String];
    unsigned char result[16];
    CC_MD5(cStr, (unsigned int)strlen(cStr), result);
    
    NSString *md5Result = [NSString stringWithFormat:
                           @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
                           result[0], result[1], result[2], result[3],
                           result[4], result[5], result[6], result[7],
                           result[8], result[9], result[10], result[11],
                           result[12], result[13], result[14], result[15]
                           ];
    return md5Result;
}

- (NSString *)cacheFolder
{
    return @"yh_url_cache";
}

- (void)deleteCacheFolder
{
    NSString *path = [NSString stringWithFormat:@"%@/%@", self.diskPath, [self cacheFolder]];
    NSFileManager *fileManager = [[NSFileManager alloc] init];
    [fileManager removeItemAtPath:path error:nil];
}

- (NSString *)cacheFilePath:(NSString *)fileName
{
    NSString *path = [[NSString alloc] initWithFormat:@"%@/%@", self.diskPath, self.cacheFolder];
    
    NSFileManager *fm = [[NSFileManager alloc] init];
    
    BOOL isDir;
    if ([fm fileExistsAtPath:path isDirectory:&isDir] && isDir) {
        
    } else {
        [fm createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
    }
    
    return [path stringByAppendingPathComponent:fileName];
}

- (NSCachedURLResponse *)cachedDataForRequest:(NSURLRequest *)request
{
    NSString *url = request.URL.absoluteString;
    NSString *urlWithOtherInfo = [[NSString alloc] initWithFormat:@"%@-otherInfo", url];
    
    NSString *filePath = [self cacheFilePath:[self md5Hash:url]];
    NSString *otherInfoPath = [self cacheFilePath:[self md5Hash:urlWithOtherInfo]];
    
    NSDate *date = [NSDate date];
    
    NSFileManager *fm = [[NSFileManager alloc] init];
    if ([fm fileExistsAtPath:filePath]) {
        BOOL expire = NO;
        
        NSDictionary *otherInfo = [self.memCache objectForKey:[otherInfoPath lastPathComponent]];
        if (!otherInfo) {
            otherInfo = [[NSDictionary alloc] initWithContentsOfFile:otherInfoPath];
            if (otherInfo) {
                [self.memCache setObject:otherInfo forKey:[otherInfoPath lastPathComponent]];
            }
        }
        
        if (self.cacheDuration > 0) {
            NSInteger createTime = [otherInfo[@"time"] integerValue];
            if (createTime + self.cacheDuration < date.timeIntervalSince1970) {
                expire = YES;
            }
        }
        
        if (!expire) {
            // Getting data from cache
            NSData *data = [self.memCache objectForKey:[filePath lastPathComponent]];
            if (!data) {
                data = [[NSData alloc] initWithContentsOfFile:filePath];
                if (data) {
                    [self.memCache setObject:data forKey:[filePath lastPathComponent]];
                }
            }
            
            NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
                                                                MIMEType:otherInfo[@"MIMEType"]
                                                   expectedContentLength:data.length
                                                        textEncodingName:otherInfo[@"textEncodingName"]];
            NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data];
            return cachedResponse;
        } else {
            // Removing old data
            
            [fm removeItemAtPath:filePath error:nil];
            [fm removeItemAtPath:otherInfoPath error:nil];
            
            [self.memCache removeObjectForKey:[filePath lastPathComponent]];
            [self.memCache removeObjectForKey:[otherInfoPath lastPathComponent]];
        }
    }
    
    return nil;
}

#pragma mark - Public
- (void)addCacheUrl:(NSString *)url forKey:(id)object
{
    NSMutableArray *array = self.webViewCacheUrls[object];
    if (!array) {
        array = @[].mutableCopy;
        self.webViewCacheUrls[object] = array;
    }
    
    if (array.count == 0
        || [array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF = %@", url]].count == 0) {
        
        [array addObject:url];
    }
}

- (void)removeUrlsForKey:(id)object
{
    [self.webViewCacheUrls removeObjectForKey:object];
}

@end