聊聊NSCache

打算了解一下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完成数据的缓存,初始化与使用与上述的AFNetworkingSDWebImage都很类似,基本原理相同此处不做赘述了。

4.遇到问题

NSCachetotalCostLimit设置了为什么没有生效?

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是无用的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容

  • 起落之间 过去的一周,是我生活、思绪、情绪无比混乱的一周,好在我始终在用自我疗愈,好在还没有到需要心理医生的地步,...
    像拉拉一样奋斗阅读 72评论 0 0
  • 第一幅自己独立完成的作品,还知道不留白,可以!
    猪猪家的小狗狗妹阅读 154评论 0 1
  • 今天这节数学课,我觉得上得很成功。班上所有学生都能跟着我的节奏一起来,包括平时压根儿不怎么听课的周康、刘家旺这节课...
    我与你的对白阅读 748评论 0 1
  • 不好,又要迟到了,上学第一天不能迟到啊!上学第一天不能迟到啊!要是迟到了又要被老爸挨批了! ...
    美絮阅读 187评论 0 0