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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

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