【原】iOS學習之PINCache第三方緩衝架構,iospincache

來源:互聯網
上載者:User

【原】iOS學習之PINCache第三方緩衝架構,iospincache

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

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 記憶體對象的儲存為KVStore for Redis。

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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.