前言:
   最近面试时,问到了限定并发数的视频下载,当时回答的时通过GCD_barrier 处理,回来想想也可以通过NSCache处理,所以顺便复习一下,这个知识点。

一,关于NSCache说明

说明文档:

@interface NSCache : NSObject
Description

A mutable collection you use to temporarily store transient key-value pairs that are subject to eviction when resources are low.

Cache objects differ from other mutable collections in a few ways:

  • 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.

You typically use NSCache objects to temporarily store objects with transient data that are expensive to create. Reusing these objects can provide performance benefits, because their values do not have to be recalculated. However, the objects are not critical to the application and can be discarded if memory is tight. If discarded, their values will have to be recomputed again when needed.

Objects that have subcomponents that can be discarded when not being used can adopt the NSDiscardableContent protocol to improve cache eviction behavior. By default, NSDiscardableContent objects in a cache are automatically removed if their content is discarded, although this automatic removal policy can be changed. If an NSDiscardableContentobject is put into the cache, the cache calls discardContentIfPossible on it upon its removal.

SDKs iOS 4.0+, macOS 10.6+, tvOS 9.0+, watchOS 2.0+
Declared In Foundation
More Class Reference

大概翻译:

  一个可变集合,用于临时存储在资源不足时可能被收回的临时键-值对。

  缓存对象与其他可变集合的不同之处在于:

   * NSCache类集成了各种自动回收策略,这些策略确保缓存不会占用太多系统内存。如果其他应用程序需要内存,这些策略将从缓存中删除一些项,从而最小化内存占用。

     * 您可以从不同的线程在缓存中添加、删除和查询条目,而不必自己锁定缓存。

* 与NSMutableDictionary对象不同,缓存不会复制放入其中的关键对象。

  您通常使用NSCache对象来临时存储具有临时数据的对象,这些数据创建起来非常昂贵。重用这些对象可以提供性能优势,因为它们的值不必重新计算。但是,这些对象对应用程序并不重要,如果内存紧张,可以丢弃它们。如果丢弃,则需要重新计算它们的值。

  如果对象的子组件在不使用时可以被丢弃,则可以采用NSDiscardableContent协议来改进缓存回收行为。默认情况下,尽管可以更改此自动删除策略, 如果缓存中的NSDiscardableContent对象的内容被丢弃,则会自动删除它们。如果NSDiscardableContentobject被放入缓存,缓存将在它被移除时对其调用discardContentIfPossible。

重点信息:

  • NSCache是一个类似NSDictionary一个可变的集合。
  • 提供了可设置缓存的数目与内存大小限制的方式。
  • 保证了处理的数据的线程安全性。在多线程操作中,不需要对Cache加锁
  • 缓存使用的key不需要是实现NSCopying的类。
  • 当内存警告时内部自动清理部分缓存数据。

二,NSCacheAPI

#import <Foundation/NSObject.h>
@class NSString;
@protocol NSCacheDelegate; NS_ASSUME_NONNULL_BEGIN NS_CLASS_AVAILABLE(10_6, 4_0)
@interface NSCache <KeyType, ObjectType> : NSObject {
@private //私有变量
id _delegate;
void *_private[5];
void *_reserved;
}
@property (copy) NSString *name; //缓存的名字

@property (nullable, assign) id<NSCacheDelegate> delegate;  //delegate代理属性

- (nullable ObjectType)objectForKey:(KeyType)key; //返回与键值关联的对象
- (void)setObject:(ObjectType)obj forKey:(KeyType)key; // 0 cost,在缓存中设置指定键名对应的值,与可变字典不同,缓存对象不会对键名做 copy 操作0成本。
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g; //在缓存中设置指定键名对应的值,并且指定该键值对的成本,成本 (cost) 用于计算记录在缓冲中的所有对象的总成本,成本可以自行指定。
- (void)removeObjectForKey:(KeyType)key; //删除缓存中,指定键名的对象。 - (void)removeAllObjects; //删除缓存中所有对象。 @property NSUInteger totalCostLimit; //缓存空间的最大总成本,超出上限会自动回收对象 默认值是 0,表示没有限制。
@property NSUInteger countLimit;  //能够缓存对象的最大数量,默认值是 0,表示没有限制。; 

@end 

@protocol NSCacheDelegate <NSObject> 

@optional 
- (void)cache:(NSCache *)cache willEvictObject:(id)obj; //缓存将要删除对象时调用,不能在此方法中修改缓存。 @end NS_ASSUME_NONNULL_END

三,举例验证

#import "ViewController.h"

@interface ViewController () <NSCacheDelegate>

@property (nonatomic, strong)NSCache *cache;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
self.cache = [[NSCache alloc]init]; /** 创建对象.*/
self.cache.delegate = self; /** 设置代理方法.*/
self.cache.countLimit = 5; /** 设置能够缓存对象的最大数.*/ [self save:@1 key:@"data1"];
[self save:@2 key:@"data2"];
[self save:@3 key:@"data3"];
[self save:@4 key:@"data4"];
[self save:@5 key:@"data5"];
[self save:@6 key:@"data6"];
} - (void)save:(id)objc key:(NSString *)key {
[self.cache setObject:objc forKey:key];
NSLog(@"save:%@",objc);
} /**
* @brief 缓存将要删除对象时调用
* @param cache 缓存对象
* @param obj 删除的对象
*/
- (void)cache:(NSCache *)cache willEvictObject:(id)obj {
NSLog(@"evict:%@",obj);
}
@end

打印结果:

2018-11-29 14:47:27.032630+0800 数组内容为空[6655:467226] save:1
2018-11-29 14:47:27.033117+0800 数组内容为空[6655:467226] save:2
2018-11-29 14:47:27.033661+0800 数组内容为空[6655:467226] save:3
2018-11-29 14:47:27.034004+0800 数组内容为空[6655:467226] save:4
2018-11-29 14:47:27.034173+0800 数组内容为空[6655:467226] save:5
2018-11-29 14:47:27.034555+0800 数组内容为空[6655:467226] evict:1
2018-11-29 14:47:27.035314+0800 数组内容为空[6655:467226] save:6

示例1.缓存池的使用
 CYWImageCache.h

#import <Foundation/Foundation.h>

@interface CYWImageCache : NSObject

@property (nonatomic, copy)NSString *name;    /**   图片名称.*/
@property (nonatomic, copy)NSString *icon; /** 图标.*/
@property (nonatomic, copy)NSString *download; /** 下载地址.*/ + (instancetype) appWithDict:(NSDictionary *)dict; @end

CYWImageCache.m

#import "CYWImageCache.h"

@implementation CYWImageCache

+ (instancetype)appWithDict:(NSDictionary *)dict {
CYWImageCache *image = [[CYWImageCache alloc]init];
[image setValuesForKeysWithDictionary:dict];
return image;
} @end

ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end

ViewController.m

#import "ViewController.h"
#import "CYWImageCache.h" @interface ViewController () <UITableViewDelegate,UITableViewDataSource>
/** plist文件数据容器.*/
@property (nonatomic, strong) NSArray *imageList;
/** 管理下载的全局队列.*/
@property (nonatomic, strong) NSOperationQueue *opqueue;
/** 所有下载的缓存池.*/
@property (nonatomic, strong) NSMutableDictionary *operationCache;
/** 保存所有图像的缓存.*/
@property (nonatomic, strong) NSCache *imageCache; @property (nonatomic, strong) UITableView *tableView; @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; } #pragma mark -- Layout Methods - (NSArray *)imageList {
if (!_imageList) {
NSArray *dictArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]pathForResource:@"image" ofType:@".plist" ]];
NSMutableArray *arrayM = @[].mutableCopy;
for (NSDictionary *dict in dictArray) {
CYWImageCache *image = [[CYWImageCache alloc]init];
[image setValuesForKeysWithDictionary:dict];
[arrayM addObject:image];
} _imageList = arrayM;
} return _imageList;
} - (NSOperationQueue *)opqueue {
if (!_opqueue) {
_opqueue = [[NSOperationQueue alloc]init];
}
return _opqueue;
} - (NSMutableDictionary *)operationCache {
if (!_operationCache) {
_operationCache = [[NSMutableDictionary alloc]init];
}
return _operationCache;
} - (NSCache *)imageCache {
if (!_imageCache) {
_imageCache = [[NSCache alloc]init];
}
return _imageCache;
} - (UITableView *)tableView {
if (!_tableView) {
_tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];
_tableView.delegate = self;
_tableView.dataSource = self;
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:NSStringFromClass(UITableViewCell.class)];
}
return _tableView;
} #pragma mark --Delegate - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.imageList.count;
} - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass(UITableViewCell.class)]; CYWImageCache *image = [[CYWImageCache alloc]init];
cell.textLabel.text = image.name;
cell.detailTextLabel.text = image.description; if ([self.imageCache objectForKey:image.name]) {
NSLog(@"图片已经下载。。。。。。");
cell.imageView.image = [self.imageCache objectForKey:image.icon]; } else {
//内存无图片
//如果沙盒里有图片,直接从沙盒里加载
UIImage *images = [UIImage imageWithContentsOfFile:[self cachePathWithUrl:image.name]];
if (images) { //沙盒有图片
NSLog(@"直接从沙盒加载图片");
//1.设置图片缓存到内存, 方便下次直接从内存加载
[self.imageCache setObject:images forKey:image.name];
//2.显示图片
cell.imageView.image = [self.imageCache objectForKey:image.icon];
} else {
//显示占位图
cell.imageView.image = [UIImage imageNamed:@"user_default"];
//下载图片
[self downloadImage:indexPath];
}
} return cell;
} -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // 点击后查看操作缓冲池内的操作详情
NSLog(@"%@", self.operationCache);
} #pragma mark --Private Methods - (NSString *)cachePathWithUrl:(NSString *)urlStr {
//将图片网址名称作为最后一项
//1.获取缓存数据
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
//2.把路径根urlStr拼接起来
return [cachePath stringByAppendingString:urlStr.lastPathComponent];
} - (void)downloadImage:(NSIndexPath *)indexpath { CYWImageCache *imageCache = self.imageList[indexpath.row];
/**
如果下载缓冲池里面有当前图片的下载操作,就不用创建下载操作,没有才创建
缓冲池字典中 key:存放当前图片的url,字符串类型。
Value:保存下载操作
*/
if (self.operationCache[imageCache.icon]) {
NSLog(@"正在玩命下载中......");
return;
}
// 缓冲池没有下载操作 // 异步下载图片
__weak typeof(self) weakSelf = self;
NSBlockOperation *downLoadOp = [NSBlockOperation blockOperationWithBlock:^{
// 模拟延时
[NSThread sleepForTimeInterval:2];
NSLog(@"正在下载中......"); // 1. 下载图片(二进制数据)
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageCache.icon]];
UIImage *image = [UIImage imageWithData:data]; // 2. 将下载的数据保存到沙盒
// 字典的赋值不能为nil,赋值为nil会崩溃
if (image) {
// 先保存到图片缓存
[weakSelf.imageCache setObject:image forKey:imageCache.icon]; // 将图片写入到沙盒
[data writeToFile:[self cachePathWithUrl:imageCache.icon] atomically:YES];
} // 3 将操作从缓冲池删除——将下载好的图片操作从缓冲池中移除
[weakSelf.operationCache removeObjectForKey:imageCache.icon]; // 4. 在主线程更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[weakSelf.tableView reloadRowsAtIndexPaths:@[indexpath] withRowAnimation:UITableViewRowAnimationNone];
/** reload 会重新调用cell的初始化方法, 会重新判断模型里面是否有图像
有的话就直接显示
*/
}];
}]; // 将操作添加到队列
[weakSelf.opqueue addOperation:downLoadOp];
NSLog(@"操作的数量------------->%ld", self.opqueue.operationCount); // 将操作添加到缓冲池中(使用图片的url作为key)
[weakSelf.operationCache setObject:downLoadOp forKey:imageCache.icon]; } /**
在真实开发中,一定要注意这个方法
*/
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning]; // 需要在这里做一些内存清理的工作,如果不处理会被系统强制闪退
// 清理内存
[self.imageCache removeAllObjects]; // 清理操作的缓存
[self.operationCache removeAllObjects]; // 取消下载队列内的任务
[self.opqueue cancelAllOperations];
} @end

SDWebimage.h 缓存池使用--图片缓存

// 继承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;
}

四,NSCache总结

* 为什么使用NSCache?
    NSCache的好处在于当系统资源耗尽时,它可以自动删减缓存;如果采用NSDictionary,那么就要自己编写程序在收到系统发出的“低内存”通知时手动删减缓存。那么都能实现要求为什么还要使用NSCache呢?
(1) 首先NSCache是资源耗尽时自动删减缓存,比起NSDictionary来说简单;
(2)它是Foundation框架的一部分,所以它能在更深层次上处理效率会更好;
(3)NSCache会先行删减“最久未使用”的对象,若自己为字典添加此功能会很复杂。
  (4)  NSCache是线程安全的,再不编写锁代码时,多个线程可以同时访问NSCache
(5)NSCache不会拷贝键 ,而是强引用键,因为键大部分是由不支持拷贝的对象来充当。

* NSCache在系统资源耗尽时会自动删减内存,那我可以控制什么时候删减内存吗?
  我们可以使用NSCache的两个与系统资源相关的尺寸来操控缓存删减起内容的时机。
(1)缓存可就收的对象总数即countLimit属性,
(2)所有对象的“总开销”即totalCostLimit属性,我们在将对象加入缓存时,可以指定“”开销值“。当对象或总开销超过上限时,缓存可能会删减其中的对象,也可能在系统资源紧缺时删减
  注意:可能删减,意味着不一定会删除,所以想通过调整开销值迫使缓存删减对象的情况下,不应使用NSCache;

* 我们在任何时候都使用NSCache就好了?
    使用任何方案都要了解它的利弊,对于“开销值”这个控制删减内容的尺寸的使用有一些注意事项,在向内存添加对象时,应该很容易计算出它的“开销值”(也就是大小),才应该是用这个尺度,例如加入缓存的事NSData对象,可以将它的大小当作“开销值”,因为不必计算,读取NSData的大小就可以了。例如必须访问磁盘才能确定文件的大小,或是必须访问数据库才能决定具体取值就不应使用“开销值”。
stackoverflow上的说明:如果可以在运行时重新创建的值(通过从Internet下载,通过计算,无论如何),NSCache可以满足您的需要。 如果无法重新创建数据(例如用户输入,时间敏感等),则不应将其存储在NSCache中,因为它将在那里被销毁。

* 怎样使用它?
(1)基本了解和使用
   我们先介绍一个与它配合使用的NSPurgeableData,NSPurgeableData是NSMutableData类的子类,实现了NSDiscardableContent协议。当系统资源紧张时,可以把保存为NSPurgeableData对象的内存释放掉。

   需要访问NSPurgeable对象,可调用beginContentAccess方法,表示不应丢弃自己占的内存。用完之后,调用endContentAccess方法,表示在必要时可以丢弃自己占的内存。记住只有对象引用计数为0时才可以丢弃。
因为缓存中NSPurgeableData对象在被系统丢弃时,会自动从缓存中移除,NSCache的evictsObjectsWithDiscardedContent属性用于标志是否开启此功能。

* 我想知道是否最好使用一个NSCache的全局实例,或者是每个需要它的组件的几个实例。
 例如,我有几个视图子类将内容绘制到图像中并缓存它们,以避免一直重新生成它们。 每个类都有一个NSCache实例,还是整个应用程序只有一个集中的NSCache?
请注意,我不是每个实例的一个缓存一个缓存。 我在说每个类的NSCache的一个实例。

回答1:我通常会为每种类型的缓存对象投一个缓存。 这样,您可以为每个例如不同的countLimit值。 要指定“保持最近呈现的50个缩略图”,“保留最近下载的5个最大的图像”,“保留最近下载的10个PDF”等。

对于真正计算上昂贵的任务,我还采用两层缓存,NSCache来实现最佳性能,并将其保存到本地文件系统中的临时/缓存目录中,以避免昂贵的下载周期,同时不消耗RAM。

回答2:如果缓存的内容在组件之间共享,那么可以使用统一的缓存 - 查找不会显着增加,至少可以减少缓存对象的冗余副本。
但是,如果缓存的内容对于每个组件是唯一的,则将它们混合在高速缓存中是无意义的,并且从代码可读性的角度来看可能会令人困惑。 在这种情况下保持缓存分开。 这也让您更精确地控制它们 - 例如 您可以更积极地从不被立即使用的组件中缓存。

总结:如果缓存的内容在组件间共享可以创建一个统一的缓存,如果不是组件间共享建议为每种类型的内容单独创建缓存,这样我们可以细粒度的控制他们,分别设置他们的countLimit等;而且我们可以设计两级缓存

iOS NSCache缓存类的了解的更多相关文章

  1. IOS缓存之NSCache缓存

    NSCache:专门做缓存的类 NSCache简介:NSCache是苹果官方提供的缓存类,用法与NSMutableDictionary的用法很相似,在AFNetworking和SDWebImage中, ...

  2. ios开发缓存处理类NSCash类的了解与使用

    一:NSCash的基本了解 #import "ViewController.h" @interface ViewController ()<NSCacheDelegate&g ...

  3. iOS缓存类的设计

    使用执行速度缓存的程序可以大大提高程序,设计一个简单的缓存类并不需要太复杂的逻辑. 只需要一个简单的3接口. 存款对象 以一个对象 删除对象 阅读对象 watermark/2/text/aHR0cDo ...

  4. iOS开发系统类功能划分

    0.OC语法基础 CHOCBase Object C语法学习笔记(一) Object C语法学习笔记(二) 1.UI类 自定义控件程序运行流程 setNeedsLayOut和setNeedsDispl ...

  5. iOS 通用缓存:HanekeSwift

    iOS 通用缓存:HanekeSwift Haneke 是个采用 Swift 编写的轻量级 iOS 通用缓存.示例: 初始化一个数据缓存: let cache = Cache<NSData> ...

  6. iOS网络图片缓存详解

    在开发移动应用的时候比如Android,IOS,因为手机流量.网速.内存等这些因素,当我们的移动应用是针对互联网,并要频繁访问网络的话,对网络优化这块就显得尤为重要了. 比如某个应用要经常显示网络图片 ...

  7. NSCache缓存怎么来的

    什么是NSCache NSCache主要用来存储临时数据(键值对),当内存资源不够时,系统会自动释放部分数据.它有三个特点: • NSCache为了保持不占用过多的系统内存,它有多种自动回收内存策略: ...

  8. ASP.NET Core 折腾笔记二:自己写个完整的Cache缓存类来支持.NET Core

    背景: 1:.NET Core 已经没System.Web,也木有了HttpRuntime.Cache,因此,该空间下Cache也木有了. 2:.NET Core 有新的Memory Cache提供, ...

  9. 分享个 之前写好的 android 文件流缓存类,专门处理 ArrayList、bean。

    转载麻烦声明出处:http://www.cnblogs.com/linguanh/ 目录: 1,前序 2,作用 3,特点 4,代码 1,前序  在开发过程中,client 和 server 数据交流一 ...

随机推荐

  1. mysql 线程等待时间,解决sleep进程过多的办法

    如果你没有修改过MySQL的配置,缺省情况下,wait_timeout的初始值是28800.   wait_timeout 过大有弊端,其体现就是MySQL里大量的SLEEP进程无法及时释放,拖累系统 ...

  2. linux内核剖析(八)进程间通信之-管道

    管道 管道是一种两个进程间进行单向通信的机制. 因为管道传递数据的单向性,管道又称为半双工管道. 管道的这一特点决定了器使用的局限性.管道是Linux支持的最初Unix IPC形式之一,具有以下特点: ...

  3. mongodb 复杂查询之 本表 join

      mongdb 的数据介绍: 系统有多个用户,contractId 代表用户Id,其中 serialno 也是一种 id,代表该客户登录系统的编号,该 contractId 每次登录系统都会产生不同 ...

  4. OpenCV自带dnn的Example研究(3)— object_detection

    这个博客系列,简单来说,今天我们就是要研究 https://docs.opencv.org/master/examples.html下的 6个文件,看看在最新的OpenCV中,它们是如何发挥作用的. ...

  5. 判断回文字符串、回文链表、回文数(python实现)

    所谓回文字符串,就是正读和反读都一样的字符串,比如"level"或者"noon"等等就是回文串.即是对称结构 判断回文字符串 方法一: def is_palin ...

  6. windows nginx配置https访问

    本文主要记录在windows下安装nginx 环境:win10-64位. 1.  到nginx官网上下载相应的安装包,http://nginx.org/en/download.html: 下载进行解压 ...

  7. 【Unity/Kinect】手势识别Gesture

    在Unity的AssetStore官方商店下载Kinect v2 Examples案例包,参考KinectDemos/GestureDemo这个文件夹下的例子. 自定义一个类,实现KinectGest ...

  8. Android gradle 配置

    gradle https://www.cnblogs.com/qianxudetianxia/p/4948499.html flavor https://blog.csdn.net/user11223 ...

  9. 消息中间件系列五:RabbitMQ的使用场景(异步处理、应用解耦)

    一.异步处理 场景: 用户注册,写入数据库成功以后,发送邮件和短信. 准备工作: 1)安装RabbitMQ,参考前面的文章 2)新建一个名为RabbitMQAsyncProc的maven web工程, ...

  10. A股滚动净利润增速最高排名

    最近2年(共8个季度)的滚动净利润都在增长,且平均增速超过10%. 计算举例:滚动净利润增速 = ((2018Q1 到 2018Q4的净利润之和) / (2017Q4 到 2018Q3的净利润之和) ...