【原】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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...