iOS -- 构建缓存时选用 NSCache (32)

构建缓存时选用 NSCache

 开发 Mac OS X 或 iOS 应用程序时,经常会遇到一个问题,那就是从网上下载的图片应如何来缓存,NSCache 类就是 Foundation 框架专为处理这种任务而设计的.

 NSCache 的优点在于,当系统资源将要耗尽时,它可以自动删减缓存,此外,NSCache 还会先行删减'最久未使用的对象'. 

另外,NSCache 是线程安全的,意思就是:在开发者自己不编写加锁代码的前提下,多个线程便可以同时访问 NSCache, 对缓存来说,线程安全非常重要,因为开发者可能要在某个线程中读取数据,此时如果发现缓存里面找不到指定的键,那么就下载该键所对应的数据了,而下载完数据之后所要执行的回调函数,有可能会放在背景线程中运行, 这样的话,就等于是用另外一个线程来写入缓存 了. 

开发者可以操控缓存删减其内容的时机.有两个与系统资源相关的尺度可供调整,  其一是缓存的对象总数,  其二是所有对象的 '总开销', 开发者在将对象加入缓存时, 可为其指定 '开销值', 当对象总数 或 总开销 超过上限时,缓存就可能会删减其中的对象了, 在可用的系统资源趋于紧张时, 也会这么做,然而要注意, '可能'会删减某个对象, 并不意味着 '一定'会删减这个对象, 删减对象时所遵循的顺序, 由具体实现来定,这尤其说明: 想通过调整 '开销值'来迫使缓存优先删减某对象, 不是个好办法. 

向缓存中添加对象时,只有在很快计算出 '开销值'的情况下,才应该采用'总开销'这个尺度,若是计算过程很复杂,那么照这种方式来使用缓存就达不到最佳效果了.因为每次向缓存 中放入对象,还要专门花时间来计算这个附加因素的值, 而缓存的本意则是要增加应用程序响应用户操作的速度.比方说,如果计算'开销值'时必须访问磁盘才能确定文件大小, 或是必须访问数据库才能决定具体数值, 那就不好了, 但是,如果要加入缓存中的是 NSData 对象, 那么就不妨指定 '开销值',可以把数据大小当做 '开销值'来用,因为 NSData 对象的数据大小是 已知的,所及计算'开销值'的过程只不过是读取一项属性. 例如:  

#import// 网络访问类

typedef void(^NetworkFetcherCompletionHandler)(NSData * data);

@interface NetworkFetcher : NSObject

- (id)initWithURL:(NSURL *)url;

- (void)startWithCompletionHandler:(NetworkFetcherCompletionHandler)handler;

@end

// 用户网络获取和缓存结果的类

@interface NSCacheClass : NSObject

@end

@implementation NSCacheClass{

       NSCache * _cache;

}

- (id)init{

        if (self = [super init]){

        _cache = [NSCache new];

        // 缓存的最大值是 100 条 URL

       _cache.countLimit = 100;

      // 5MB 的 总开销 限制.   

      _cache.totalCostLimit = 5 * 1024 * 1024;

     }

       return self;

}

- (void)downloadDataForURL:(NSURL *)url{

          NSData * cachedData = [_cache objectForKey:url];

          if(cachedData){

                [self useData:cachedData];

          }else{

               NetworkFetcher * fetcher = [[NetworkFetcher alloc]initWithURL:url];

               [fetcher startWithCompletionHandler:^(NSData * data){

                          [_cache setobject:data forKey:url cost:data.length];

                          [self useData:data];

                }];

           }

}

@end

在本例中, 下载数据所用的 URL ,就是缓存的键, 若缓存不存在, 则下载数据并将其放入缓存, 而数据的'开销值'则为其长度, 创建 NSCache 时,将其中可缓存的总对象数目上限设为 100,将'总开销'上限设为 5MB ,由于'开销量'以字节为单位, 所以要通过算式将 MB 换算成 字节.

还有个类叫做 NSPurgeableData (可清除的 data), 和 NSCache 搭配起来用, 效果很好,此类是 NSMutableData 的子类,而且实现 NSDiscardableContent 协议(可丢弃的内容协议), 如果某个对象所占的内存能够根据需要随时丢弃, 那么就可以实现该协议所定义的接口, 这就是说, 当系统资源紧张时, 可以把保存 NSPurgeableData 对象的那块内存释放掉, NSDiscardableContent 协议里面定义了名为 isContentDiscarded 的方法, 可以用来查询相关内存是否已经释放.

如果需要访问某个 NSPuargeableData 对象, 可以调用其 beginContentAccess 方法, 告诉它现在还不应丢弃自己所占据的内存, 用完之后, 调用 endContentAccess 方法, 告诉它在必要的时候可以丢弃自己所占据的那块内存, 这些调用可以嵌套, 所以说,衙门就像递增递减引用计数一样,只有对象的 '引用计数'为 0 的时候才可以丢弃.

如果将 NSPurgeableData 对象加入 NSCathe ,那么当该对象为系统所丢弃时, 也会自动从缓存中移除, 通过 NSCache 的 evictsObjectsWithDiscardedContent (移除废弃的内容) 属性, 可以开启或关闭此 功能.

刚才的例子可以改写为:

- (void)downloadDataForURL:(NSURL *)url{

            NSPurgeableData * cachedData = [_cache objectForKey:url];

             if(cachedData){

                    [cacheData beginContentAccess];

                    [self useData:cachedData];

                    [cacheData endContentAccess];

            }else{

                   NetworkFetcher * fetcher = [[NetworkFetcher alloc]initWithURL:url];

                  [fetcher startWithCompletionHandler:^(NSData * data){

                            NSPurgeableData * purgeableData = [NSPurgeableData dataWithData:data];

                           [_cache setobject:data forKey:url cost:purgeableData.length];

                           [self useData:data];

                           [purgeableData endContentAccess];

                  }];

           }

}

注意: 创建好了 NSPurgeableData 对象之后, 其 'purge 引用计数' 会多一, 所以无须再调用 beginContentAccess 了, 然而其后必须调用 endContentAccess ,将多出来的 "1" 抵消.

总结:

NSCathe 提供了自动删减功能, 而且是'线程安全的'.

可以给 NSCache 对象设置上限, 用以限制缓存中的对象总个数 及 '总成本',而这些尺度则定义了 缓存删减其中对象的时机. 但是绝对不要把这些尺度当成可靠的 '硬限制',它们仅对 NSCache 其指导作用.

将 NSPurgeableData 与 NSCache 搭配使用, 可实现自动清除数据的功能, 也就是说, 当 NSPurgeableData 对象所占据的内存为系统所丢弃时, 该对象自身也会从缓存中移除.

如果缓存使用得当, 那么应用程序的响应速度就能提高, 只有那种 '重新计算起来很麻烦的'数据,才值得放入缓存, 比如那些需要从网络获取或 从 磁盘读取的数据.

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

推荐阅读更多精彩内容