iOS NSCache缓存类的了解
前言:
最近面试时,问到了限定并发数的视频下载,当时回答的时通过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:
You typically use 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缓存类的了解的更多相关文章
- IOS缓存之NSCache缓存
NSCache:专门做缓存的类 NSCache简介:NSCache是苹果官方提供的缓存类,用法与NSMutableDictionary的用法很相似,在AFNetworking和SDWebImage中, ...
- ios开发缓存处理类NSCash类的了解与使用
一:NSCash的基本了解 #import "ViewController.h" @interface ViewController ()<NSCacheDelegate&g ...
- iOS缓存类的设计
使用执行速度缓存的程序可以大大提高程序,设计一个简单的缓存类并不需要太复杂的逻辑. 只需要一个简单的3接口. 存款对象 以一个对象 删除对象 阅读对象 watermark/2/text/aHR0cDo ...
- iOS开发系统类功能划分
0.OC语法基础 CHOCBase Object C语法学习笔记(一) Object C语法学习笔记(二) 1.UI类 自定义控件程序运行流程 setNeedsLayOut和setNeedsDispl ...
- iOS 通用缓存:HanekeSwift
iOS 通用缓存:HanekeSwift Haneke 是个采用 Swift 编写的轻量级 iOS 通用缓存.示例: 初始化一个数据缓存: let cache = Cache<NSData> ...
- iOS网络图片缓存详解
在开发移动应用的时候比如Android,IOS,因为手机流量.网速.内存等这些因素,当我们的移动应用是针对互联网,并要频繁访问网络的话,对网络优化这块就显得尤为重要了. 比如某个应用要经常显示网络图片 ...
- NSCache缓存怎么来的
什么是NSCache NSCache主要用来存储临时数据(键值对),当内存资源不够时,系统会自动释放部分数据.它有三个特点: • NSCache为了保持不占用过多的系统内存,它有多种自动回收内存策略: ...
- ASP.NET Core 折腾笔记二:自己写个完整的Cache缓存类来支持.NET Core
背景: 1:.NET Core 已经没System.Web,也木有了HttpRuntime.Cache,因此,该空间下Cache也木有了. 2:.NET Core 有新的Memory Cache提供, ...
- 分享个 之前写好的 android 文件流缓存类,专门处理 ArrayList、bean。
转载麻烦声明出处:http://www.cnblogs.com/linguanh/ 目录: 1,前序 2,作用 3,特点 4,代码 1,前序 在开发过程中,client 和 server 数据交流一 ...
随机推荐
- Linux学习必备
17,继往开来 实践是检验真理的唯一标准! ---运维技术组----mvpbang #开源代码 https://github.com/ #目前最受欢迎的 https://gitee.com/ ...
- Effective Java 第三版——64. 通过对象的接口引用对象
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- 浅谈 CSS 预处理器: 为什么要使用预处理器?
CSS 自诞生以来,基本语法和核心机制一直没有本质上的变化,它的发展几乎全是表现力层面上的提升.最开始 CSS 在网页中的作用只是辅助性的装饰,轻便易学是最大的需求:然而如今网站的复杂度已经不可同日而 ...
- 恶心github 下载慢
起因 某天看github上面的代码,有点不耐烦,想下载下来再看,但是现在速度慢的可怜 解决思路 相关网站 获取域名相关ip ipaddress.com 这个有好处就是知道网站部署在哪里,如果有vpn的 ...
- js正则匹配html标签中的style样式和img标签
<!DOCTYPE html> <html> <head> <title></title> </head> <body&g ...
- 无法加载协定为“ServiceReference1.xxxxxx”的终结点配置部分,因为找到了该协定的多个终结点配置。请按名称指示首选的终结点配置部分。
原因是在web.config 文件中多次引用了“添加外部引用” <system.serviceModel> <bindings> <basicHttpBinding> ...
- C++ char 类型:字符型和最小的整型
C++ 中没有 byte,Java 中有 byte. 但是 C++ 有 char,char 是可用来放整数的最小字节类型. #include <iostream> int main() { ...
- 搞明白GOROOT,GOPATH,GOBIN,project目录
我们接下来一个一个来看关于Go语言中的三个目录的详细解释先通过go env查看go的环境变量(我这里是mac的环境,所以可能和你的不同) localhost:~ zhaofan$ go env GOA ...
- java web service 写入图片到web/img/
获取本类service路径,然后字符串截取和拼接 String classpath= this.getClass().getResource("/").getPath(); Str ...
- window/linux composer安装/卸载
packagist库:https://packagist.org/ window 安装 参考地址:https://www.kancloud.cn/thinkphp/composer/35668 1. ...