手機應用發展到今天,用戶的體驗至關重要,有時決定著應用產品的生死,比如滑動一個商品列表時,用戶自然地希望列表的滑動跟隨手指,如絲般順滑,如果卡頓,不耐煩的用戶就會點退出按鈕,商品也就失去了展示機會;
而當一個用戶發現自己裝了某個APP后流量用的特別快,Ta可能會永遠將這個APP打入冷宮。想要優化界面的響應、節省流量,本地緩存對用戶而言是透明的,卻是必不可少的一環。
設計本地緩存并不是開一個數組或本地數據庫,把數據丟進去就能達到預期效果的,這是因為:
- 內存讀寫快,但容量有限,圖片容易丟失;
- 磁盤容量大,圖片“永久”保存,但讀寫較慢。
這對計算機與生俱來的矛盾,導致緩存設計必須將兩種存儲方式組合使用,加上iOS系統平臺特性,無形中增加了本地緩存系統的復雜度,本篇來看看 SDWebImage 是如何實現一個流暢的緩存系統的。
SDWebImage 本地緩存的整體流程如下:
緩存數據的格式
在深入具體的讀寫流程之前,先了解一下存儲數據的格式,這有助于我們理解后續的操作步驟:
- 為了加快界面顯示的需要,
內存緩存
的圖片用UIImage
; -
磁盤緩存
的是NSData
,是從網絡下載到的原始數據
。
寫入流程
存入圖片時,調用入口方法:
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock
先寫入 SDMemoryCache :
[self.memCache setObject:image forKey:key cost:cost];
再寫入磁盤,由 ioQueue 異步執行:
- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key
讀取流程
讀取圖片時,調用入口方法為:
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock
首先從內存緩存中獲取:
UIImage *image = [self imageFromMemoryCacheForKey:key];
如果內存中有,直接返回給外部調用者;當內存緩存獲取失敗時,從磁盤獲取圖片文件數據:
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
解碼為 UIImage:
diskImage = [self diskImageForKey:key data:diskData options:options];
并寫回內存緩存,再返回給調用者。
磁盤緩存
磁盤緩存位于沙盒的 Caches 目錄
下:/Library/Caches/default/com.hackemist.SDWebImageCache.default/
,
保證了緩存圖片在下次啟動還存在,又不會被iTunes備份。
文件名由cachedFileNameForKey
生成,使用Key(即圖片URL)的MD5值,順便說明一下,圖片的Key還有其他作用:
- 作為獲取緩存的索引
- 防止重復寫入
寫入過程很簡單:
- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key
利用 NSData 的文件寫入方法:
[imageData writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil];
內存緩存
SDMemoryCache 是繼承 NSCache 實現的,占用空間是用像素值來統計的(SDCacheCostForImage),因為 NSCache 的totalCostLimit 并不嚴格(關于 NSCache 的一些特性,請參考被忽視和誤解的NSCache),用像素計算可以方
便預估和加快運算。
輔助內存緩存 weakCache
你可能從看前面流程圖時,就好奇這個輔助內存緩存的作用是什么,這是由于收到內存警告時,NSCache 里的圖片可能已經被系統清除,但實際圖片還是被界面上的 ImageView 保留著,因此在 weakCache 再保存一份,遇到這種情況時,只要簡單地將 weakCache 中的值寫回 NSCache 即可,這樣提高了緩存命中率,也避免在界面保有圖片時,緩存系統的誤判,導致重復下載或從磁盤加載圖片。
weakCache 由 NSMapTable 實現,因為普通的NSDictionary無法分別對Key強引用,對值弱引用,即 weakCache 利用對 UIImage 的弱引用,可以判斷是否被緩存以外的對象使用,是本地緩存加倍順滑的關鍵喔。
總結
SDMemoryCache 的本地緩存很好地平衡了內存和磁盤的優缺點,最大限度利用了系統本身提供的 NSCache 和 NSData 的原生方法,巧妙地利用 weak 屬性判斷 UIImage 是否被引用問題,為我們開發提供了值得借鑒的思路。