iOS学习之PINCache第三方缓存框架

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

1、PINCache概述


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

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

  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)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.  

@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.

@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.  

@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)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.  

@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.  

@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.  

@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. 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.

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进行业务层的封装时,要保证更新操作的串行化,避免并行更新操作的情况。

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

推荐阅读更多精彩内容