打算了解一下NSCache可能要从前一段时间面试讲起,当时面试者问我了解NSCache吗?我的第一印象是:这是什么类,怎么从来没听过,难道他说的是NSURLCache?于是跟他扯了一通h5的离线缓存的实现。面试完回来一查直接傻眼了,因此做一次学习记录吧。
1.NSCache简述
An NSCache object is a mutable collection that stores key-value pairs, similar to an NSDictionary object. The NSCache class provides a programmatic interface to adding and removing objects and setting eviction policies based on the total cost and number of objects in the cache.
- The NSCache class incorporates various auto-eviction policies, which ensure that a cache doesn’t use too much of the system’s memory. If memory is needed by other applications, these policies remove some items from the cache, minimizing its memory footprint.
- You can add, remove, and query items in the cache from different threads without having to lock the cache yourself.
- Unlike an NSMutableDictionary object, a cache does not copy the key objects that are put into it.
NSCache
是一个类似NSDictionary
一个可变的集合。- 提供了可设置缓存的数目与内存大小限制的方式。
- 保证了处理的数据的线程安全性。
- 缓存使用的key不需要是实现
NSCopying
的类。- 当内存警告时内部自动清理部分缓存数据。
2.NSCache使用
NSCache *cache = [[NSCache alloc] init];
cache.delegate = self;
//cache.countLimit = 50; // 设置缓存数据的数目
//cache.totalCostLimit = 5 * 1024 * 1024; // 设置缓存的数据占用内存大小
- (void)start:(id)sender{
for(int i = 0;i < 1000;i++){
NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"1" ofType:@"pptx"]];
// 1.缓存数据
[cache setObject:data forKey:[NSString stringWithFormat:@"image_%d",arc4random()]];
}
}
#pragma mark - NSCacheDelegate
- (void)cache:(NSCache *)cache willEvictObject:(id)obj{
NSLog(@"删除缓存数据");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
NSLog(@"内存警告");
}
执行结果:
2017-06-18 22:51:58.204455 InterView[1113:223367] 删除缓存数据
2017-06-18 22:51:58.204812 InterView[1113:223367] 删除缓存数据
2017-06-18 22:51:58.205198 InterView[1113:223367] 删除缓存数据
2017-06-18 22:51:58.205521 InterView[1113:223367] 删除缓存数据
2017-06-18 22:51:58.205918 InterView[1113:223367] 删除缓存数据
2017-06-18 22:51:58.206216 InterView[1113:223367] 删除缓存数据
2017-06-18 22:52:05.207987 InterView[1113:223367] 内存警告
3.NSCache的使用场景
3.1 AFNetworking(2.X)中UIImageView+AFNetworking的图片缓存
// 缓存的key使用请求的路径
static inline NSString * AFImageCacheKeyFromURLRequest(NSURLRequest *request) {
return [[request URL] absoluteString];
}
// 继承NSCache,实现自定义的cache策略
@interface AFImageCache : NSCache <AFImageCache>
@end
@implementation AFImageCache
- (UIImage *)cachedImageForRequest:(NSURLRequest *)request {
switch ([request cachePolicy]) {
case NSURLRequestReloadIgnoringCacheData:
case NSURLRequestReloadIgnoringLocalAndRemoteCacheData:
return nil;
default:
break;
}
return [self objectForKey:AFImageCacheKeyFromURLRequest(request)];
}
- (void)cacheImage:(UIImage *)image
forRequest:(NSURLRequest *)request
{
if (image && request) {
[self setObject:image forKey:AFImageCacheKeyFromURLRequest(request)];
}
}
@end
AFImageCache具体的使用
+ (id <AFImageCache>)sharedImageCache {
static AFImageCache *_af_defaultImageCache = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_af_defaultImageCache = [[AFImageCache alloc] init];
// 收到内存警告直接清理掉缓存
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * __unused notification) {
[_af_defaultImageCache removeAllObjects];
}];
});
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
return objc_getAssociatedObject(self, @selector(sharedImageCache)) ?: _af_defaultImageCache;
#pragma clang diagnostic pop
}
AF 3.0及以上已经替换了实现的方式(NSMutableDictionary + GCD保证线程安全),有兴趣可以直接自己看一下3.0源码。
3.2 SDWebImage中SDImageCache图片缓存
// 继承NSCache,实现自定义的cache类
@interface AutoPurgeCache : NSCache
@end
@implementation AutoPurgeCache
- (id)init
{
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
@end
AutoPurgeCache的使用
初始化
// Init the memory cache
_memCache = [[AutoPurgeCache alloc] init];
_memCache.name = fullNamespace;
缓存图片与取缓存图片
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
return [self.memCache objectForKey:key];
}
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key {
// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
return image;
}
// Second check the disk cache...
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.shouldCacheImagesInMemory) {
// 计算需要缓存的内存空间
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
return diskImage;
}
3.3 React Native(0.38)
RCTAsyncLocalStorage
数据缓存类RCTImageCache
图片缓存类
二者都使用到NSCache
完成数据的缓存,初始化与使用与上述的AFNetworking
与SDWebImage
都很类似,基本原理相同此处不做赘述了。
4.遇到问题
NSCache
的totalCostLimit
设置了为什么没有生效?
demo中的例子我把cache.totalCostLimit = 5 * 1024 * 1024;
注释打开,执行发现直到内存警告才开始自动清理数据?尝试了很多次都是一样的结果。那设置的5M的最大的缓存大小为什么没有起到作用呢?重新查看一下苹果的文档关于totalCostLimit
的描述:
Discussion:
If 0, there is no total cost limit. The default value is 0.
When you add an object to the cache, you may pass in a specified cost for the object, such as the size in bytes of the object. If adding this object to the cache causes the cache’s total cost to rise above totalCostLimit, the cache may automatically evict objects until its total cost falls below totalCostLimit. The order in which the cache evicts objects is not guaranteed.
This is not a strict limit, and if the cache goes over the limit, an object in the cache could be evicted instantly, at a later point in time, or possibly never, all depending on the implementation details of the cache.
注意加粗部分,是需要使用如下的接口吗?
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;
动手尝试将demo中的setObject换成如下实现,发现执行一次就已经触发了自动清理缓存的回调,也基本验证了这一点。
NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"1" ofType:@"pptx"]];
[cache setObject:data forKey:[NSString stringWithFormat:@"image_%d",arc4random()] cost:10 * 1024 * 1024];
回头查看AFNetworking
以及SDWebImage
以及RN中的两处缓存的使用,也充分印证了这一点:设置全局缓存实例时如果设置了totalCostLimit
必然存储缓存的方法调用必然带上了cost
,否则totalCostLimit
是无用的。
网友评论