NSCache介绍及使用

构建缓存时选用NSCache而非NSDictionary

在写 程序时,经常遇到从网络上下载图片的问题,比如客户要求应用上的图片他们可以在后台修改等,那从网上下载的图片如何来缓存呢?

一开始我们的做法是将图片存到一个NSMutableDictionary中,这样稍后使用时就无需下载了,但是Foundation还给我们提供了一个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属性用于标志是否开启此功能。

stackoverflow上的介绍

// Your cache should have a lifetime beyond the method or handful of methods
// that use it. For example, you could make it a field of your application
// delegate, or of your view controller, or something like that. Up to you.
NSCache *myCache = ...;
NSAssert(myCache != nil, @"cache object is missing");

// 在cache外尝试获取cache中的对象
Widget *myWidget = [myCache objectForKey: @"Important Widget"];
if (!myWidget) {

//如果cache中没有或被移除了,我们必须创建它;
//可以推测创建是个耗资源的操作(这是为什么我们使用cache),如果不耗资源我们可能不需要cache
    myWidget = [[[Widget alloc] initExpensively] autorelease];


//把它放进缓存,他可能永久存在,也可能在任何时候被移除,
//那样我们必须再下一次使用时创建它
    [myCache setObject: myWidget forKey: @"Important Widget"];
}

// myWidget 存在,使用它
myWidget =  [myCache objectForKey:@“Important Widget"”];
if (myWidget) {

    [myWidget runOrWhatever];
}

(2)封装好的NSCache,
本人也封装了一个NSCache:https://github.com/onebutterflyW/NSCache.git

(3)cache.h的头文件中说明
属性:

@property (copy) NSString *name;//缓存的名字
@property NSUInteger totalCostLimit;// 缓存空间的最大总成本,超出上限会自动回收对象 默认值是 0,表示没有限制。
@property NSUInteger countLimit;//能够缓存对象的最大数量,默认值是 0,表示没有限制。
@property BOOL evictsObjectsWithDiscardedContent;//标示缓存是否回收废弃的内容,默认值是 YES,表示自动回收.
方法:
- (id)objectForKey:(id)key;//返回与键值关联的对象
- (void)setObject:(id)obj forKey:(id)key; // 0 cost,在缓存中设置指定键名对应的值,与可变字典不同,缓存对象不会对键名做 copy 操作0成本。
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g;//在缓存中设置指定键名对应的值,并且指定该键值对的成本,成本 (cost) 用于计算记录在缓冲中的所有对象的总成本,成本可以自行指定。
- (void)removeObjectForKey:(id)key;//删除缓存中,指定键名的对象。
- (void)removeAllObjects;//删除缓存中所有对象。
NSCacheDelegate
(void)cache:(NSCache *)cache willEvictObject:(id)obj;//缓存将要删除对象时调用,不能在此方法中修改缓存。

总结:
实现缓存时应选用NSCache而非NSDictionary对象。因为NSCache可以提供优雅的自动删减功能,而且是线程安全的,此外,它与字典不同,并不会拷贝键。
可以给NSCache对象设置上限,用以限制缓存中的对象总个数及总成本,而这些尺度则定义了缓存删减其中对象的时机。但是绝对不要把这些尺度当成可靠的硬限制,他们仅对NSCache起指导作用。
将NSPurgeableData与NSCache搭配使用,可实现自动清除数据的功能,也就是说,当NSPurgeableData对象所占内存为系统丢弃时,该对象自身也会从缓存中移除。
如果缓存使用得当。那么应用程序的响应速度就能提高。只有那种重新计算起来很费事的数据,才值得放入缓存,比如那些需要从网络获取或从磁盘读取的数据。

实例:在使用大量图片的app中,需要从存储里面读取数据,每次都从文件系统里面读取文件会造成卡顿现象。
解决办法就是把NSData对象缓存起来,先从NSCache里面读取数据,然后再从文件系统获取数据,提高效率。通过这样的方式,形成了 内存 -> 文件系统 -> 网络图片 的三级图片访问系统。

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

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

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员的修养

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值