【原】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框架进行地图开发,利用这种 ...
随机推荐
- Easyui 设置datagrid 进入编辑状态,保存结束编辑
在datagrid中如何实现让一行进入编辑状态,修改数据后,保存信息呢? //点击列表变成文本框,进入可编辑状态 $(function () { var doc = $(document), tabl ...
- 关于SQL语句查询区分大小写
在需要区分大小的字段后添加:collate Chinese_PRC_CS|CI_AS|AI CI表示:不区分大小写 CS表示:区分大小写 AI表示: 指定不区分重音 AS表示:指定区分重音. 例:查 ...
- python Unicode 编码解码
1 #将Unicode转换成普通的Python字符串:"编码(encode)" 2 unicodestring = u"Hello world" 3 utf8s ...
- java 写文本换行
import org.apache.commons.io.FileUtils; public static void main(String[] args) throws IOException { ...
- 【阿里云配置端口开放】使用 iptables
要知道: 1.目前(16年-12-10)阿里云主机只要有服务开启,所有端口是默认开启的.这样很不好,安全做法是,需要开启外网端口时,由开发人员去配置. 2.想要开放端口,就需要使用iptables命令 ...
- C语言简单文法
<源程序>→<外部声明>|<外部声明><函数体> <外部申明>→<头文件><函数声明>|其他声明 <函数体&g ...
- shell脚本比较两个数大小
#/bin/bash read -p "请输入第一个数:" a read -p "请输入第二个数:" b if [ $a -gt $b ] #判断第一个数是不是 ...
- WPF 变量绑定实现
最近初学WPF,遇到如控件的内容是动态生成的.这时候就需要变量绑定. 简单写下变量绑定的步骤. 如下面的 例子,TextBlock 的内容是动态的,绑定变量StuName. <TextBlock ...
- JS WEB 交互问题
1 webView----->js #import "ViewController.h" @interface ViewController () @property (no ...
- Nginx 相关
删除 access.log 之后,要让 Nginx 重新加载一下,命令 killall -s USR1 nginx 无需新建 access.log ,这个命令会自动创建该文件 Nginx 的日志文件轮 ...