美文网首页
iOS -- 构建缓存时选用 NSCache (32)

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

作者: dingzhijie | 来源:发表于2017-10-19 15:20 被阅读0次

    构建缓存时选用 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 对象所占据的内存为系统所丢弃时, 该对象自身也会从缓存中移除.

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

    相关文章

      网友评论

          本文标题:iOS -- 构建缓存时选用 NSCache (32)

          本文链接:https://www.haomeiwen.com/subject/yuguuxtx.html