在项目中总是需要缓存一些网络请求数据以减轻服务器压力,业内也有许多优秀的开源的解决方案。通常的缓存方案都是由内存缓存和磁盘缓存组成的,内存缓存速度快容量小,磁盘缓存容量大速度慢可持久化。

1、PINCache概述

  PINCache 是 Pinterest 的程序员在 Tumblr 的 TMCache 基础上发展而来的,主要的改进是修复了 dealock 的bug,TMCache 已经不再维护了,而 PINCache 最新版本是v3.0.1。

  PINCache是多线程安全的,使用键值对来保存数据。PINCache内部包含了2个类似的对象属性,一个是内存缓存 PINMemoryCache,另一个是磁盘缓存 PINDiskCache,具体的操作包括:get,set,remove,trim,都是通过这两个内部对象来完成。

  PINCache本身并没有过多的做处理缓存的具体工作,而是全部交给它内部的2个对象属性来实现,它只是对外提供了一些同步或者异步接口。在iOS中,当App收到内存警告或者进入后台的时候,PINCache能够清理掉所有的内存缓存。

2、PINCache的实现方式

  • 原理

  采用 PINCache 项目的 Demo 来说明,PINCache 是从服务器加载数据,再缓存下来,继而做业务逻辑处理,如果下次还需要同样的数据,要是缓存里面还有这个数据的话,那么就不需要再次发起网络请求了,而是直接使用这个数据。

  PINCache 采用 Disk(文件) + Memory(其实就是NSDictionary) 的双存储方式,在cache数据的管理上,都是采用键值对的方式进行管理,其中 Disk 文件的存储路径形式为:APP/Library/Caches/com.pinterest.PINDiskCache.(name),Memory 内存对象的存储为键值存储。

  PINCache 除了可以按键取值、按键存值、按键删值之外,还可以移除某个日期之前的缓存数据、删除所有缓存、限制缓存大小等。在执行 set 操作的同时会记录文件/对象的更新date 和 成本cost,对于 date 和 cost 两个属性,有对应的API允许开发者按照 date 和 cost 清除 PINCache 管理的文件和内存,如清除某个日期之前的cache数据,清除cost大于X的cache数据等。

  在Cache的操作实现上,PINCache采用dispatch_queue+dispatch_semaphore 的方式,dispatch_queue 是并发队列,为了保证线程安全采用 dispatch_semaphore 作锁,从bireme的这篇文章中了解到,dispatch_semaphore 的优势在于不会轮询状态的改变,适用于低频率的Disk操作,而像Memory这种高频率的操作,反而会降低性能。

  • 同步操作Cache

  同步方式阻塞访问线程,直到操作成功:

/// @name Synchronous Methods

/**
This method determines whether an object is present for the given key in the cache. @see containsObjectForKey:block:
@param key The key associated with the object.
@result YES if an object is present for the given key in the cache, otherwise NO.
*/
- (BOOL)containsObjectForKey:(NSString *)key; /**
Retrieves the object for the specified key. This method blocks the calling thread until the object is available.
Uses a lock to achieve synchronicity on the disk cache. @see objectForKey:block:
@param key The key associated with the object.
@result The object for the specified key.
*/
- (__nullable id)objectForKey:(NSString *)key; /**
Stores an object in the cache for the specified key. This method blocks the calling thread until the object has been set.
Uses a lock to achieve synchronicity on the disk cache. @see setObject:forKey:block:
@param object An object to store in the cache.
@param key A key to associate with the object. This string will be copied.
*/
- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key; /**
Removes the object for the specified key. This method blocks the calling thread until the object
has been removed.
Uses a lock to achieve synchronicity on the disk cache. @param key The key associated with the object to be removed.
*/
- (void)removeObjectForKey:(NSString *)key; /**
Removes all objects from the cache that have not been used since the specified date.
This method blocks the calling thread until the cache has been trimmed.
Uses a lock to achieve synchronicity on the disk cache. @param date Objects that haven't been accessed since this date are removed from the cache.
*/
- (void)trimToDate:(NSDate *)date; /**
Removes all objects from the cache. This method blocks the calling thread until the cache has been cleared.
Uses a lock to achieve synchronicity on the disk cache.
*/
- (void)removeAllObjects;
  • 异步操作Cache

  异步方式具体操作在并发队列上完成后会根据传入的block把结果返回出来:

/// @name Asynchronous Methods

/**
This method determines whether an object is present for the given key in the cache. This method returns immediately
and executes the passed block after the object is available, potentially in parallel with other blocks on the
<concurrentQueue>. @see containsObjectForKey:
@param key The key associated with the object.
@param block A block to be executed concurrently after the containment check happened
*/
- (void)containsObjectForKey:(NSString *)key block:(PINCacheObjectContainmentBlock)block; /**
Retrieves the object for the specified key. This method returns immediately and executes the passed
block after the object is available, potentially in parallel with other blocks on the <concurrentQueue>. @param key The key associated with the requested object.
@param block A block to be executed concurrently when the object is available.
*/
- (void)objectForKey:(NSString *)key block:(PINCacheObjectBlock)block; /**
Stores an object in the cache for the specified key. This method returns immediately and executes the
passed block after the object has been stored, potentially in parallel with other blocks on the <concurrentQueue>. @param object An object to store in the cache.
@param key A key to associate with the object. This string will be copied.
@param block A block to be executed concurrently after the object has been stored, or nil.
*/
- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key block:(nullable PINCacheObjectBlock)block; /**
Removes the object for the specified key. This method returns immediately and executes the passed
block after the object has been removed, potentially in parallel with other blocks on the <concurrentQueue>. @param key The key associated with the object to be removed.
@param block A block to be executed concurrently after the object has been removed, or nil.
*/
- (void)removeObjectForKey:(NSString *)key block:(nullable PINCacheObjectBlock)block; /**
Removes all objects from the cache that have not been used since the specified date. This method returns immediately and
executes the passed block after the cache has been trimmed, potentially in parallel with other blocks on the <concurrentQueue>. @param date Objects that haven't been accessed since this date are removed from the cache.
@param block A block to be executed concurrently after the cache has been trimmed, or nil.
*/
- (void)trimToDate:(NSDate *)date block:(nullable PINCacheBlock)block; /**
Removes all objects from the cache.This method returns immediately and executes the passed block after the
cache has been cleared, potentially in parallel with other blocks on the <concurrentQueue>. @param block A block to be executed concurrently after the cache has been cleared, or nil.
*/
- (void)removeAllObjects:(nullable PINCacheBlock)block;

3、PINDiskCache

  • DiskCache有以下属性:
@property (readonly) NSString *name;//指定的cache名称,如MyPINCacheName,在Library/Caches/目录下

@property (readonly) NSURL *cacheURL;//cache目录URL,如Library/Caches/com.pinterest.PINDiskCache.MyPINCacheName,这个才是真实的存储路径

@property (readonly) NSUInteger byteCount;//disk存储的文件大小

@property (assign) NSUInteger byteLimit;//disk上允许存储的最大字节

@property (assign) NSTimeInterval ageLimit;//存储文件的最大生命周期

@property (nonatomic, assign, getter=isTTLCache) BOOL ttlCache;//TTL强制存储,如果为YES,访问操作不会延长该cache对象的生命周期,如果试图访问一个生命超出self.ageLimit的cache对象时,会当做该对象不存在。
  • 为了遵循Cocoa的设计哲学,PINCache还允许用户自定义block用以监听add,remove操作事件,不是KVO,却似KVO:
/// @name Event Blocks

/**
A block to be executed just before an object is added to the cache. The queue waits during execution.
*/
@property (copy) PINDiskCacheObjectBlock __nullable willAddObjectBlock; /**
A block to be executed just before an object is removed from the cache. The queue waits during execution.
*/
@property (copy) PINDiskCacheObjectBlock __nullable willRemoveObjectBlock; /**
A block to be executed just before all objects are removed from the cache as a result of <removeAllObjects:>.
The queue waits during execution.
*/
@property (copy) PINDiskCacheBlock __nullable willRemoveAllObjectsBlock; /**
A block to be executed just after an object is added to the cache. The queue waits during execution.
*/
@property (copy) PINDiskCacheObjectBlock __nullable didAddObjectBlock; /**
A block to be executed just after an object is removed from the cache. The queue waits during execution.
*/
@property (copy) PINDiskCacheObjectBlock __nullable didRemoveObjectBlock; /**
A block to be executed just after all objects are removed from the cache as a result of <removeAllObjects:>.
The queue waits during execution.
*/
@property (copy) PINDiskCacheBlock __nullable didRemoveAllObjectsBlock;

  对应 PINCache 的同步异步两套API,PINDiskCache 也有两套实现,不同之处在于同步操作会在函数开始加锁,函数结尾释放锁,而异步操作只在对关键数据操作时才加锁,执行完后立即释放,这样在一个函数内部可能要完成多次加锁解锁的操作,这样提高了PINCache的并发操作效率,但对性能也是一个考验。

4、PINMemoryCache

  • PINMemoryCache的属性:
@property (readonly) NSUInteger totalCost;//开销总数

@property (assign) NSUInteger costLimit;//允许的内存最大开销

@property (assign) NSTimeInterval ageLimit;//same as PINDiskCache

@property (nonatomic, assign, getter=isTTLCache) BOOL ttlCache;//same as PINDiskCache

@property (assign) BOOL removeAllObjectsOnMemoryWarning;//内存警告时是否清除memory cache 

@property (assign) BOOL removeAllObjectsOnEnteringBackground;//App进入后台时是否清除memory cache 

5、操作安全性

  • PINDiskCache的同步API
- (void)setObject:(id)object forKey:(NSString *)key fileURL:(NSURL **)outFileURL {

...

[self lock];

//1.将对象 archive,存入 fileURL 中

//2.修改对象的访问日期为当前的日期
//3.更新PINDiskCache成员变量  

[self unlock]; 

}

  整个操作都是在lock状态下完成的,保证了对disk文件操作的互斥

  其他的objectForKey,removeObjectForKey操作也是这种实现方式。

  • PINDiskCache的异步API
- (void)setObject:(id)object forKey:(NSString *)key block:(PINDiskCacheObjectBlock)block {

     __weak PINDiskCache *weakSelf = self;

    dispatch_async(_asyncQueue, ^{//向并发队列加入一个task,该task同样是同步执行PINDiskCache的同步API

        PINDiskCache *strongSelf = weakSelf;

        [strongSelf setObject:object forKey:key fileURL:&fileURL];

        if (block) {

            [strongSelf lock];

        NSURL *fileURL = nil;

            block(strongSelf, key, object, fileURL);

            [strongSelf unlock];
}});
}
  • PINMemoryCache的同步API
- (void)setObject:(id)object forKey:(NSString *)key withCost:(NSUInteger)cost {

    [self lock];

    PINMemoryCacheObjectBlock willAddObjectBlock = _willAddObjectBlock;

    PINMemoryCacheObjectBlock didAddObjectBlock = _didAddObjectBlock;

    NSUInteger costLimit = _costLimit;

    [self unlock];

    if (willAddObjectBlock)

        willAddObjectBlock(self, key, object);

    [self lock];

    _dictionary[key] = object;//更新key对应的object

    _dates[key] = [[NSDate alloc] init];

    _costs[key] = @(cost);

    _totalCost += cost;

    [self unlock];//释放lock,此时在并发队列上的别的操作如objectForKey可以获取同一个key对应的object,但是拿到的都是同一个对象

    ...

}

  PINMemoryCache 的并发安全性依赖于 PINMemoryCache 维护了一个NSMutableDictionary,每一个 key-value 的 读取和设置 都是互斥的,即信号量保证了这个 NSMutableDictionary 的操作是线程安全的,其实Cocoa的容器类如NSArray,NSDictionary,NSSet都是线程安全的,而NSMutableArray,NSMutableDictionary则不是线程安全的,所以这里在对PINMemoryCache的NSMutableDictionary进行操作时需要加锁互斥。

  那么假如从 PINMemoryCache 中根据一个 key 取到的是一个 mutable 的Collection对象,就会出现如下情况:

   1)线程A和B都读到了一份value,NSMutableDictionary,它们是同一个对象

   2)线程A对读出的NSMutableDictionary进行更新操作

   3)线程B对读出的NSMutableDictionary进行更新操作

  这就有可能导致执行出错,因为NSMutableDictionary不是线程安全的,所以在对PINCache进行业务层的封装时,要保证更新操作的串行化,避免并行更新操作的情况。

【原】iOS学习之PINCache第三方缓存框架的更多相关文章

  1. 【原】iOS学习47之第三方-FMDB

    将 CocoaPods 安装后,按照 CocoaPods 的使用说明就可以将 FMDB 第三方集成到工程中,具体请看博客iOS学习46之第三方CocoaPods的安装和使用(通用方法) 1. FMDB ...

  2. IOS学习:常用第三方库(GDataXMLNode:xml解析库)

    IOS学习:常用第三方库(GDataXMLNode:xml解析库) 解析 XML 通常有两种方式,DOM 和 SAX: DOM解析XML时,读入整个XML文档并构建一个驻留内存的树结构(节点树),通过 ...

  3. 【原】iOS学习之Masonry第三方约束

    1.Masonry概述 目前最流行的Autolayout第三方框架 用优雅的代码方式编写Autolayout 省去了苹果官方恶心的Autolayout代码 大大提高了开发效率 框架地址:https:/ ...

  4. 【原】iOS学习46之第三方CocoaPods的安装和使用(通用方法)

    本文主要说明CocoaPods的安装步骤.使用说明和常见的报错即解决方法. 1. CocoaPods 1>  CocoaPods简介 CocoaPods是一个用来帮助我们管理第三方依赖库的工具. ...

  5. 【原】iOS学习之第三方-AFNetworking1.3.0

    将 CocoaPods 安装后,按照 CocoaPods 的使用说明就可以将 AFNetworking 第三方集成到工程中,具体请看上篇博客iOS学习46之第三方CocoaPods的安装和使用(通用方 ...

  6. iOS缓存框架-PINCache解读

    文/Amin706(简书作者)原文链接:http://www.jianshu.com/p/4df5aad0cbd4著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”. 在项目中总是需要缓存一 ...

  7. iOS常用第三方开源框架和优秀开发者博客等

    博客收藏iOS开发过程好的开源框架.开源项目.Xcode工具插件.Mac软件.文章等,会不断更新维护,希望对你们有帮助.如果有推荐或者建议,请到此处提交推荐或者联系我. 该文档已提交GitHub,点击 ...

  8. iOS开发-常用第三方开源框架介绍

    iOS开发-常用第三方开源框架介绍 图像: 1.图片浏览控件MWPhotoBrowser        实现了一个照片浏览器类似 iOS 自带的相册应用,可显示来自手机的图片或者是网络图片,可自动从网 ...

  9. iOS学习笔记20-地图(二)MapKit框架

    一.地图开发介绍 从iOS6.0开始地图数据不再由谷歌驱动,而是改用自家地图,当然在国内它的数据是由高德地图提供的. 在iOS中进行地图开发主要有三种方式: 利用MapKit框架进行地图开发,利用这种 ...

随机推荐

  1. 关于es5的一些新方法

    1.数组方法(1)isArray在之前我们判断数组类型的数据都是用instanceof来判断的,es5新增了对数组的判断,即Array.isArray()(2)every和some这两个方法一般用于对 ...

  2. jquery.nicescroll.js可全屏可改滚动条颜色的滚动条插件-推荐

    有一款很棒的插件 http://www.ijquery.cn/?p=666

  3. 我的LaTeX中文文档模板

    中文LaTeX处理模板 环境MiTex内核 编辑环境WinEdit 源码如下: \documentclass[a4paper,12pt]{article} \usepackage{CJK} %设定字号 ...

  4. Design Tiny URL

    Part 1: 前言: 最近看了一些关于短址(short URL)方面的一些博客,有些博客说到一些好的东西,但是,也不是很全,所以,这篇博客算是对其它博客的一个总结吧. 介绍: 短址,顾名思义,就是把 ...

  5. 点击页面判断是否安装app并打开,否则跳转app store的方法

    常常有这样的场景,咱们开发出来的APP需要进行推广,比如在页面顶部来一张大Banner图片,亦或一张二维码.但往往我们都是直接给推广图片加了一个下载链接(App Store中的).所以咱们来模拟一下用 ...

  6. sz rz SecureCRT

    yum install lszrz apt-get install lszrz wget http://down1.chinaunix.net/distfiles/lrzsz-0.12.20.tar. ...

  7. WPF剪切板问题-OpenClipboard HRESULT:0x800401D0 (CLIPBRD_E_CANT_OPEN))

    WPF剪切板问题-OpenClipboard HRESULT:0x800401D0 (CLIPBRD_E_CANT_OPEN))       最近碰到一个问题,需要弄个小工具来解决.刚好接触到WPF, ...

  8. ffmpeg-201612[01,08,10,17,21,27,30]-bin.7z

    ESC 退出 0 进度条开关 1 屏幕原始大小 2 屏幕1/2大小 3 屏幕1/3大小 4 屏幕1/4大小 5 屏幕横向放大 20 像素 6 屏幕横向缩小 20 像素 S 下一帧 [ -2秒 ] +2 ...

  9. Django~待解决的问题

    1.一个Model.py中简单调用多个数据库,不写数据库路由

  10. 利用密钥通过ssh互访

    dir(192.168.131.132):作为client rs1(192.168.131.110)和 rs2(192.168.131.131):作为server端     1.dir 运行命令:   ...