打算了解一下NSCache可能要從前一段時間面試講起,當(dāng)時面試者問我了解NSCache嗎?我的第一印象是:這是什么類,怎么從來沒聽過,難道他說的是NSURLCache?于是跟他扯了一通h5的離線緩存的實現(xiàn)。面試完回來一查直接傻眼了,因此做一次學(xué)習(xí)記錄吧。
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一個可變的集合。- 提供了可設(shè)置緩存的數(shù)目與內(nèi)存大小限制的方式。
- 保證了處理的數(shù)據(jù)的線程安全性。
- 緩存使用的key不需要是實現(xiàn)
NSCopying的類。- 當(dāng)內(nèi)存警告時內(nèi)部自動清理部分緩存數(shù)據(jù)。
2.NSCache使用
NSCache *cache = [[NSCache alloc] init];
cache.delegate = self;
//cache.countLimit = 50; // 設(shè)置緩存數(shù)據(jù)的數(shù)目
//cache.totalCostLimit = 5 * 1024 * 1024; // 設(shè)置緩存的數(shù)據(jù)占用內(nèi)存大小
- (void)start:(id)sender{
for(int i = 0;i < 1000;i++){
NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"1" ofType:@"pptx"]];
// 1.緩存數(shù)據(jù)
[cache setObject:data forKey:[NSString stringWithFormat:@"image_%d",arc4random()]];
}
}
#pragma mark - NSCacheDelegate
- (void)cache:(NSCache *)cache willEvictObject:(id)obj{
NSLog(@"刪除緩存數(shù)據(jù)");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
NSLog(@"內(nèi)存警告");
}
執(zhí)行結(jié)果:
2017-06-18 22:51:58.204455 InterView[1113:223367] 刪除緩存數(shù)據(jù)
2017-06-18 22:51:58.204812 InterView[1113:223367] 刪除緩存數(shù)據(jù)
2017-06-18 22:51:58.205198 InterView[1113:223367] 刪除緩存數(shù)據(jù)
2017-06-18 22:51:58.205521 InterView[1113:223367] 刪除緩存數(shù)據(jù)
2017-06-18 22:51:58.205918 InterView[1113:223367] 刪除緩存數(shù)據(jù)
2017-06-18 22:51:58.206216 InterView[1113:223367] 刪除緩存數(shù)據(jù)
2017-06-18 22:52:05.207987 InterView[1113:223367] 內(nèi)存警告
3.NSCache的使用場景
3.1 AFNetworking(2.X)中UIImageView+AFNetworking的圖片緩存
// 緩存的key使用請求的路徑
static inline NSString * AFImageCacheKeyFromURLRequest(NSURLRequest *request) {
return [[request URL] absoluteString];
}
// 繼承NSCache,實現(xiàn)自定義的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];
// 收到內(nèi)存警告直接清理掉緩存
[[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及以上已經(jīng)替換了實現(xiàn)的方式(NSMutableDictionary + GCD保證線程安全),有興趣可以直接自己看一下3.0源碼。
3.2 SDWebImage中SDImageCache圖片緩存
// 繼承NSCache,實現(xiàn)自定義的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) {
// 計算需要緩存的內(nèi)存空間
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
return diskImage;
}
3.3 React Native(0.38)
RCTAsyncLocalStorage數(shù)據(jù)緩存類RCTImageCache圖片緩存類
二者都使用到NSCache完成數(shù)據(jù)的緩存,初始化與使用與上述的AFNetworking與SDWebImage都很類似,基本原理相同此處不做贅述了。
4.遇到問題
NSCache的totalCostLimit設(shè)置了為什么沒有生效?
demo中的例子我把cache.totalCostLimit = 5 * 1024 * 1024;注釋打開,執(zhí)行發(fā)現(xiàn)直到內(nèi)存警告才開始自動清理數(shù)據(jù)?嘗試了很多次都是一樣的結(jié)果。那設(shè)置的5M的最大的緩存大小為什么沒有起到作用呢?重新查看一下蘋果的文檔關(guān)于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換成如下實現(xiàn),發(fā)現(xiàn)執(zhí)行一次就已經(jīng)觸發(fā)了自動清理緩存的回調(diào),也基本驗證了這一點。
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中的兩處緩存的使用,也充分印證了這一點:設(shè)置全局緩存實例時如果設(shè)置了totalCostLimit必然存儲緩存的方法調(diào)用必然帶上了cost,否則totalCostLimit是無用的。