【原】iOS学习之PINCache第三方缓存框架
在项目中总是需要缓存一些网络请求数据以减轻服务器压力,业内也有许多优秀的开源的解决方案。通常的缓存方案都是由内存缓存和磁盘缓存组成的,内存缓存速度快容量小,磁盘缓存容量大速度慢可持久化。
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第三方缓存框架的更多相关文章
- 【原】iOS学习47之第三方-FMDB
将 CocoaPods 安装后,按照 CocoaPods 的使用说明就可以将 FMDB 第三方集成到工程中,具体请看博客iOS学习46之第三方CocoaPods的安装和使用(通用方法) 1. FMDB ...
- IOS学习:常用第三方库(GDataXMLNode:xml解析库)
IOS学习:常用第三方库(GDataXMLNode:xml解析库) 解析 XML 通常有两种方式,DOM 和 SAX: DOM解析XML时,读入整个XML文档并构建一个驻留内存的树结构(节点树),通过 ...
- 【原】iOS学习之Masonry第三方约束
1.Masonry概述 目前最流行的Autolayout第三方框架 用优雅的代码方式编写Autolayout 省去了苹果官方恶心的Autolayout代码 大大提高了开发效率 框架地址:https:/ ...
- 【原】iOS学习46之第三方CocoaPods的安装和使用(通用方法)
本文主要说明CocoaPods的安装步骤.使用说明和常见的报错即解决方法. 1. CocoaPods 1> CocoaPods简介 CocoaPods是一个用来帮助我们管理第三方依赖库的工具. ...
- 【原】iOS学习之第三方-AFNetworking1.3.0
将 CocoaPods 安装后,按照 CocoaPods 的使用说明就可以将 AFNetworking 第三方集成到工程中,具体请看上篇博客iOS学习46之第三方CocoaPods的安装和使用(通用方 ...
- iOS缓存框架-PINCache解读
文/Amin706(简书作者)原文链接:http://www.jianshu.com/p/4df5aad0cbd4著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”. 在项目中总是需要缓存一 ...
- iOS常用第三方开源框架和优秀开发者博客等
博客收藏iOS开发过程好的开源框架.开源项目.Xcode工具插件.Mac软件.文章等,会不断更新维护,希望对你们有帮助.如果有推荐或者建议,请到此处提交推荐或者联系我. 该文档已提交GitHub,点击 ...
- iOS开发-常用第三方开源框架介绍
iOS开发-常用第三方开源框架介绍 图像: 1.图片浏览控件MWPhotoBrowser 实现了一个照片浏览器类似 iOS 自带的相册应用,可显示来自手机的图片或者是网络图片,可自动从网 ...
- iOS学习笔记20-地图(二)MapKit框架
一.地图开发介绍 从iOS6.0开始地图数据不再由谷歌驱动,而是改用自家地图,当然在国内它的数据是由高德地图提供的. 在iOS中进行地图开发主要有三种方式: 利用MapKit框架进行地图开发,利用这种 ...
随机推荐
- JS判断是不是手机浏览器浏览网站的网页,并自动跳转
现在智能手机上网越来越普遍了,为了获得用户体验增加网站流量,你有必要为你的网站增加一个访问端设备的判断功能,若发现是手机用户访问,则直接跳转到手机站,通过百度的APP site,很容易就可实现这功能. ...
- calendar的一些操作
一.通过分析日期函数,根据日期进行一系列操作,例如:我们需要知道2个时间段中所有的日期等等. 由于Calendar 类是一个抽象类,因此我们不能通过new来获取该对象的实例.我们可以通过其类方法 ge ...
- oracle遇到的锁异常,oralce record is locked by another user
由于我在前不久的一次项目调试的时候,将一条数据的ID与另一条数据的ID相同了,但不知为什么没有报错,当在页面发现问题时,删除这条数据时就报错了,oralce record is locked by a ...
- ACM/ICPC 之 有流量上下界的网络流-Dinic(可做模板)(POJ2396)
//有流量上下界的网络流 //Time:47Ms Memory:1788K #include<iostream> #include<cstring> #include<c ...
- C#上传图片
//一般处理程序 public void GetImageFromWeb() { //创建文件夹 //2016-10-14 dq string filePath = "~/ProductIm ...
- ajax 设置同步
这个问题总是碰见,但是又总是记不住怎么拼写,这次直接写出来,长期保存. Ajax请求默认的都是异步的 如果想同步 async设置为false就可以(默认是true) 例如: $.ajax({ ...
- 病毒四度升级:安天AVL Team揭露一例跨期两年的电信诈骗进化史
自2014年9月起,安天AVL移动安全团队持续检测到一类基于Android移动平台的间谍类病毒,病毒样本大多伪装成名为"最高人民检察院"的应用.经过反编译逆向分析以及长期的跟踪调查 ...
- 【React】组件生命周期
初始化阶段 getDefaultPropos:只调用一次,实力之间共享引用 getInitialState:初始化每个实例特有的状态 componentWillMount:render之前最后一次修改 ...
- C++ 系列:静态库与动态库
转载自http://www.cnblogs.com/skynet/p/3372855.html 这次分享的宗旨是——让大家学会创建与使用静态库.动态库,知道静态库与动态库的区别,知道使用的时候如何选择 ...
- HDU5988 Coding Contest(费用流)
2016青岛现场赛的一题,由于第一次走过不会产生影响,需要拆点,不过比赛时没想到,此外还有许多细节要注意,如要加eps,时间卡得较紧要注意细节优化等 #include <iostream> ...