一、為什么使用緩存
緩存的目的是以空間換時間。
出于優化考慮:服務器壓力、用戶體驗、用戶流量等;
出于功能考慮:離線存儲、微信會話列表、新聞列表等;
重度使用緩存的 APP:微信、微博等。
二、iOS 上的緩存框架
NSCache、PINCache、YYCache、SDWebImage(分析 SDImageCache 部分)
1、NSCache
蘋果提供的一個簡單的內存緩存;
類似 NSDictionary 一個可變的集合;
提供了可設置緩存大小與內存大小限制的方式;
保證了處理的數據的線程安全性;
內存警告時自動清理部分緩存數據
2、PINCache
PINCache 項目是在 Tumblr 宣布不再維護 TMCache 后,由 Pinterest 維護和改進的基于 TMCache 的一個內存緩存,修復了 TMCache 存在的性能和死鎖問題。
3、YYCache
YYCache 是國內開發者 ibireme 開源的一個線程安全的高性能緩存組件。
4、SDWebImage
SDWebImage 框架通過給 UIImageView 和 UIButton 添加分類,實現了一個異步下載圖片并且支持緩存的功能。整個框架的接口簡潔,分工明確。
三、線程安全
這些緩存框架都是線程安全的。
多線程操作共享數據不會出現意想不到的結果就是線程安全的,否則則是不安全的。多個線程同時訪問或讀取同一共享數據,每個線程的讀到的數據是一樣的,則不存在線程不安全。如果多個線程對同一資源進行讀寫操作,那么每個線程讀到的結果不可預料,線程是不安全的。
接下來:
1、iOS 中有哪些方法保證線程安全;
2、這些緩存框架是如何保證線程安全的。
四、iOS 開發中的鎖
1、OSSPinLock
2、dispatch_semaphore
3、pthread_mutex ?pthread_mutex(recursive) ?NSRecursiveLock
4、NSLock
5、NSCondition ?NSConditionLock
6、@synchronized
YYCache 和 PINCache 在內存緩存使用的是 pthread_mutex,在磁盤緩存使用的是Semaphone。 SDWebImage 在內存緩存使用的是 NSCache,本身就是線程安全的。在磁盤緩存使用串行隊列來保證線程安全。
五、緩存
1、緩存的讀取
緩存讀取的邏輯大致為:
先訪問內存緩存,再訪問磁盤緩存(寫入、讀取、查詢、刪除);
讀取緩存時,如果在內存緩存中無法獲取對應的緩存,則會去磁盤緩存中尋找。如果在磁盤緩存中找到了對應的緩存,則會將該對象再次寫入內存緩存中,保證下一次嘗試獲取同一緩存時能夠在內存中就能返回,提高速度。
2、二級緩存
MemoryCache
PINMemoryCache 通過維護一個 dic 記錄 object 最后一次訪問的時間,通過排序來實現 LRU;
YYMemoryCache 緩存內部通過雙向鏈表和 NSDictionary 實現 LRU 淘汰算法;
SDImageCache 緩存使用的是 NSCache。
DiskCache
PINDiskCache 和 SDImageCache 是基于文件系統的;
YYDiskCache 采用了 SQLite&文件系統實現。
3、SDWebImage
SDImageCache 默認圖片清理時間為一周。
SDImageCache 緩存的寫入:
1、將圖片緩存在內存中;
2、判斷圖片格式是 png 或 jpeg,將圖片轉化為 NSData 數據;
3、如果是在 mac_os 系統中,直接將圖片轉化為 NSBitmapImageRep 數據;
4、獲取圖片的存儲路徑,其中圖片的文件名通過傳入的 key 經過 md5 加密后獲得的;
5、將圖片存儲在磁盤中。
SDImageCache 緩存的刪除:
1、獲取磁盤中圖片的最后修改日期;
2、根據日期將圖片進行分類,將超過最長存放時間的文件存儲在刪除數組中,其他的文件信息存儲在另一個 dic 中,并計算除去要刪除的文件之外的文件大小;
3、根據刪除數組中的文件路徑,將對應的文件刪除;
4、判斷剩下的文件大小是否超過用戶現在的最大容量;
5、如果超過,則將剩余文件按修改時間進行升序排列,刪除修改時間最早的文件,直到剩余文件大小小于最大磁盤容量。
清理時機:
系統內存不足時,會將內存中所有的圖片緩存刪除;
當系統進入后臺時,會對磁盤中的文件數據進行清理;
當收到程序關閉通知時,會對磁盤中的文件數據進行清理。
4、PINCache
PINCache 是線程安全的鍵值對緩存框架,用于緩存一些臨時數據或需要頻繁加載的數據。
PINCache 除了可以按鍵取值、按鍵存值、按鍵刪除值之外,還可以移除某個日期之外的緩存數據、刪除所有緩存、限制緩存大小。
PINMemoryCache:
維護了三個 dic,分別為_dictionary、_dates、_costs,字典的 key 相同,value 分別為對象、最后訪問日期、大小。
清理緩存:
內存警告和進入后臺時,默認自動清除所有的內存緩存。
PINDiskCache
PINDiskCache 以文件形式存儲緩存,在內存中維護了兩個 ?dic分別為 _dates、_sizes,分別存儲了文件的最后編輯時間和文件大小。
在初始化時子線程遍歷硬盤緩存初始化這兩個值,開發中可以根據業務邏輯調用api刪除硬盤緩存。
支持清理的維度:age、byte。
5、YYCache
YYCache:提供了最外層的接口,調用了 YYmemoryCache 和 YYDiskCache 的相關方法;
YYMemoryCache:負責處理容量小,相對高速的內存緩存。線程安全,支持手動和自動清理緩存等功能;
_YYLinkedMap:YYMemoryCache 使用的雙向鏈表類;
_YYLinkedMapNode:是 _YYLinkedMap 使用的節點類;
YYDiskCache:負責處理容量大,相對低速的磁盤緩存。線程安全,支持異步操作,自動和手動清理緩存等功能;
YYKVStorage:YYDiskCache 的底層實現類,用于管理磁盤緩存;
YYKVStorageItem:內置在 YYKVStorage 中,是 YYKVStorage 內部用于封裝某個緩存的類。
YYMemoryCache:
將需要緩存的對象與傳入的 key 關聯起來,類似于 NSCache。
不同于 NSCache 的是,它的內部有:
緩存淘汰算法:LRU 算法來淘汰使用頻率較低的緩存;
緩存清理策略:三個維度分別為 count(緩存數量)、cost(開銷)、age(距上一次的訪問時間)。可根據不同的需求清理某一維度超標的緩存。
無論從哪一維度清理緩存,都是從使用頻率最低的那個緩存開始清理。
在 YYMemoryCache 中,使用了雙向鏈表來保存這些緩存:
當寫入一個新的緩存時,要把這個緩存節點放到鏈表頭部,并且原鏈表頭部的緩存節點要變成現在鏈表的第二個節點;
當訪問一個已有的緩存時,要把這個緩存節點移動到鏈表的頭部,原位置兩側的緩存接上,原頭部節點變為第二個;
(根據清理維度)自動清理緩存時,要從鏈表的最后端逐個清理。
清理緩存:
內存警告和進入后臺時,默認自動清除所有的內存緩存。
YYDiskCache:
與第一級緩存相同點是:
都具有查詢、寫入、讀取、刪除緩存的接口;
不直接操作緩存,通過另一個類(YYKVStorage)來操作;
使用 LRU 算法來清理緩存;
支持 cost、count、age 三個維度清理不符合標準的緩存。
不同點是:
1、根據緩存數據的大小來采取不同的形式的緩存:
數據庫 sqlite:針對小容量緩存,緩存的 data 和元數據都保存在數據庫里;
文件+數據庫形式:針對大容量緩存,緩存的 data 寫在文件系統中,其元數據保存在數據庫中。
2、除了 cost、count、age 三個維度,還添加了一個磁盤容量的維度。
六、緩存框架的選型
由圖可見:
1、YYMemoryCache 的性能不錯,僅次于 NSDictory+OSSpinLock;
2、NSCache 的寫入性能較差。讀寫性能不錯;
3、PINMemoryCache 的讀寫性能還可以,但讀取速度差于 NSCache;
由圖可見:
1、存取小數據時(NSNumber),YYDiskCache的性能遠遠高于基于文件存儲的庫;
2、較大數據的存取性能比較接近,但得益于 SQLite 存儲的元數據,YYDiskCache 實現了 LRU 淘汰算法、更快的數據統計,更多的容量控制選項。
總結
1、選擇合適的線程鎖;
2、選擇合適的數據結構;
3、選擇合適的線程來操作不同的任務;
4、選擇合適的存儲方式;
5、選擇底層的類;
6、變量、方法的命名以及接口的設計。
ppt:https://github.com/yuetianlu/cache_ppt
參考:
https://blog.ibireme.com/2015/10/26/yycache/
https://juejin.im/post/5a657a946fb9a01cb64ee761
https://juejin.im/post/5a4080d16fb9a0451969d0aa
https://www.cnblogs.com/fengmin/p/5318782.html
https://bestswifter.com/ios-lock/