【原】iOS學習之PINCache第三方緩存框架

来源:http://www.cnblogs.com/gfxxbk/archive/2016/08/30/5821156.html
-Advertisement-
Play Games

在項目中總是需要緩存一些網路請求數據以減輕伺服器壓力,業內也有許多優秀的開源的解決方案。通常的緩存方案都是由記憶體緩存和磁碟緩存組成的,記憶體緩存速度快容量小,磁碟緩存容量大速度慢可持久化。 1、PINCache概述 PINCache 是 Pinterest 的程式員在 Tumblr 的 TMCache ...


  在項目中總是需要緩存一些網路請求數據以減輕伺服器壓力,業內也有許多優秀的開源的解決方案。通常的緩存方案都是由記憶體緩存和磁碟緩存組成的,記憶體緩存速度快容量小,磁碟緩存容量大速度慢可持久化。

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 <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進行業務層的封裝時,要保證更新操作的串列化,避免並行更新操作的情況。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • #import <UIKit/UIKit.h> @interface SearchBar : UITextField @property (nonatomic,strong) UIButton *button; + (instancetype)searchBar; @end #import "Sea ...
  • //這裡設置游標位置,讓游標位置後移10 textField.leftView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 10, 0)]; textField.leftViewMode = UITextFieldViewModeAlways; ...
  • 一:UIViewController模態跳轉 知識點1: a: 在官方文檔中,建議這兩者之間通過delegate實現交互。例如使用UIImagePickerController從系統相冊選取照片或者拍照,imagePickerController和彈出它的VC之間就通過UIImagePickerCo ...
  • 廢話不多說,先看上效果,由於動畫錄製的時候幀率限制,只能將動畫放慢了進行錄製,更容易看到效果 這是點擊開始之後代碼 由於動畫使由多個動畫組成,所以第一個動畫完畢之後自動再次開始一個動畫 先解釋一下動畫執行過程 第一步是通過CABasicAnimation 對照片進行縮放 第二步是通過CAKeyfra ...
  • 很久沒有總結,回頭看了一下過期的賬號,記錄的內容少之又少。最近有一些時間,想好好總結記錄一下。 由於很久沒有記錄,想寫的東西很多又很雜,想了一下,一篇一篇羅列知識點和經驗,還不如寫一個系列,記錄一個應用的開發流程和經歷。 主線就是一個應用的構建和開發過程,期間再針對部分節點進行分析和探討。 這篇的標 ...
  • 一:首先瞭解一下生命周期圖 二:UIViewController 生命周期介紹 1.通過alloc init 分配記憶體,初始化controller. 2.loadView loadView方法預設實現[super loadView] 如果在初始化controller時指定了xib文件名,就會根據傳入 ...
  • 一:首先查看一下關於UIGestureRecognizer的定義 UIGestureRecognizer是一個抽象類,定義了所有手勢的基本行為,使用它的子類才能處理具體的手勢 知識點1:關於UIGestureRecognizer的子類如下(下麵這些才是我們平常會直接運用到的類): 實例如下: 二:關 ...
  • 1.1 重新規劃android的項目結構 重新規劃android的目錄結構分兩步: 1.建立AndroidLib類庫,將與業務無關的邏輯轉移到AndroidLib。 acitivity存放的是跟業務無關的Activity基類 cache包存放的是緩存數據和圖片相關的處理 net包存放的是網路底層封裝 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...