1. 介绍 EGOCache
EGOCache 是一个简单的、线程安全的基于 key-value 的缓存框架,支持 NSString、UI/NSImage 和 NSData,也支持存储任何实现协议的类,可以设定缓存的过期时间,默认为1天。EGOCache 只提供了磁盘缓存,没有提供内存缓存。
2. 使用
2.1. 在 Podfile 中添加 Pod
pod 'EGOCache'
之后终端执行:
pod install --no-repo-update
2.2. 缓存数据和读取数据
- 缓存和读取 NSString
//缓存 NSString
NSString *mockString = @"the mock date";
[[EGOCache globalCache] setString:mockStr forKey:[NSString stringWithFormate:@"%lu", (unsigned long)[mockString hash]] withTimeoutInterval:60*60*24*2];
//读取 NSString
NSString *fetchString = [[EGOCache globalCache] stringForKey:[NSString stringWithFormat:@"%lu", (unsigned long)[@"mockString" hash]]];
- 缓存和读取 NSData
//缓存 NSData
NSData *mockData = [NSData data];
[[EGOCache globalCache] setData:mockData forKey:[NSString stringWithFormat:@"%lu", (unsigned long)[@"mockData" hash]] withTimeoutInterval:60*60*24*2];
//读取 NSData
NSData *fetchData = [[EGOCache globalCache] dataForKey:[NSString stringWithFormat:@"%lu", (unsigned long)[@"mockData" hash]]];
- 缓存和读取 UIImage
//缓存 UIImage
UIImage *mockImage = [UIImage imageNamed:@""];
[[EGOCache globalCache] setImage:mockImage forKey:[NSString stringWithFormate:@"lu", (unsigned long)[@"mockImage" hash]] withTimeoutInterval:60*60*24*2];
//读取 UIImage
UIImage *fetchImage = [[EGOCache globalCache] imageForKey:[NSString stringWithFormate:@"lu", (unsigned long)[@"mockImage" hash]]];
2.3. EGOCache 源码解读
从前面的例子来看,EGOCache 的易用性已经显而易见了。
不免产生了疑问:
数据是怎么进行缓存的呢?
缓存的数据是如何检测是否已经过期的呢?
打开 EGOCache 的源码,可以看到 EGOCache 是个单例类,整个程序的应用周期只初始化一次。
+ (instancetype)globalCache {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[[self class] alloc] init];
});
return instance;
}
- 缓存数据
查看 initWithCacheDirectory:
方法会发现,在每次初始化 EGOCache 对象时,首先会遍历一遍 plist 文件中所有已存在的缓存项;接着拿每个缓存项的时间(timeInterval)和当前时间做对比,若缓存项的过期时间早于当前时间,则删除对应的缓存项数据,同时删除 plist 文件中对应的 key 记录。
代码实现如下:
_cacheInfo = [[NSDictionary dictionaryWithContentsOfFile:cachePathForKey(_directory, @"EGOCache.plist")] mutableCopy];
if(!_cacheInfo) {
_cacheInfo = [[NSMutableDictionary alloc] init];
}
[[NSFileManager defaultManager] createDirectoryAtPath:_directory withIntermediateDirectories:YES attributes:nil error:NULL];
NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate];
NSMutableArray* removedKeys = [[NSMutableArray alloc] init];
for(NSString* key in _cacheInfo) {
if([_cacheInfo[key] timeIntervalSinceReferenceDate] <= now) {
[[NSFileManager defaultManager] removeItemAtPath:cachePathForKey(_directory, key) error:NULL];
[removedKeys addObject:key];
}
}
[_cacheInfo removeObjectsForKeys:removedKeys];
- 读取数据
EGOCache 在读取一个缓存项的时候,首先会判断缓存项是否存在;如果缓存项存在的话,接着去判断读取到的缓存项的存储时间和当前时间相比是否过期;如果缓存项没有过期,则返回读取到的缓存项数据。
这里有一点非常重要:判断读取到的缓存项的存储时间和当前时间相比是否过期。为什么要这么做呢?举个例子应该就清楚了。假设遇到这样一种情况:EGOCache 缓存的数据正好在被初始化之后过期,那么如果在读取缓存项的时候不去验证下数据是否过期就直接返回数据,那么这里就产生了错误。
- (BOOL)hasCacheForKey:(NSString*)key {
NSDate* date = [self dateForKey:key];
if(date == nil) return NO;
if([date timeIntervalSinceReferenceDate] < CFAbsoluteTimeGetCurrent()) return NO;
return [[NSFileManager defaultManager] fileExistsAtPath:cachePathForKey(_directory, key)];
}
- (NSDate*)dateForKey:(NSString*)key {
__block NSDate* date = nil;
dispatch_sync(_frozenCacheInfoQueue, ^{
date = (self.frozenCacheInfo)[key];
});
return date;
}
总结
回头再来看,EGOCache 的实现简洁明了,存储数据的实现思路很值得学习。