導語:這是SDWebImage源碼理解的第二篇,本篇介紹SDWebImage中的緩存相關的內容
一、概述
在SDWebImage中處理圖片緩存的是SDImageCache類。
1、SDImageCache的由來
通過圖片數據(數據可能來自磁盤,也可能來自網絡) 創建 UIImage(imageWithData:) 時,底層調用 ImageIO 的 CGImageSourceCreateWithData() 方法,在 64 位的設備上默認開啟緩存(ShouldCache為YES);在 CGImage 內部,緩存了第一次渲染到屏幕上的解碼位圖數據;只要image對象不釋放,該image對象再次顯示到屏幕時,不再需要解碼;如果這個圖片被釋放掉,其內部的解碼數據也會被立刻釋放。
在SDWebImage中,從網絡上下載的圖片,或從磁盤中取出圖片,會在子線程中解碼圖片,解碼后得到位圖圖片數據,不被UIImage緩存,需要SDWebImage自己去緩存。
2、SDImageCache簡述
SDImageCache中緩存有兩個部分,一部分是內存緩存,通過NSCache實現,以Key-Value的形式存儲圖片,當內存不夠的時候會清除所有緩存圖片;一部分是磁盤緩存,通過File System實現,在退出到后臺和應用將終止情形下,清除過期的圖片(默認保存時間大于一周的圖片)。
當SDWebImageManager向SDImageCache請求圖片資源時,先搜索內存緩存圖片,有就直接返回;沒有的話去從磁盤讀取圖片,然后解碼圖片,并將解碼結果保存在內存緩存中;如果內存和磁盤中都沒有,才去網絡請求數據。
當SDWebImageDownloader從網絡中下載圖片,將圖片解碼結果緩存到內存緩存中,將圖片數據保存在磁盤中。
二、內存緩存圖片
SDImageCache中負責內存緩存的是NSCache的子類AutoPurgeCache。
1、NSCache簡介
NSCache是蘋果官方提供的緩存類,是線程安全的,在多線程中無需對NSCache進行加鎖。NSCache的key只做強引用,無需實現NSCopying協議,不會像NSDictionary一樣復制對象。
NSCache的屬性有三個,分別是:totalCostLimit(最大cost限制)默認值是 0,表示無限制; countLimit(最大個數限制)默認值是 0,表示無限制;evictsObjectsWithDiscardedContent(是否回收廢棄的內容),默認值是 YES,表示自動回收。
-
NSCache的方法有存、取、刪除等方法,在SDImageCache中緩存圖片時,需要設置對應圖片對應的cost(setObject:forKey:cost:), 緩存里的數據量到達限制時,會觸發NSCache的清理策略,具體清除策略由NSCache內部實現,外部無法控制。
//SDImageCache中,在iphone 設備上,單個圖片的cost計算公式如下: cost = imageHeight * imageWidth * imageScale * imageScale;
NSCacheDelegate只有一個方法,將要刪除對象時調用(cache:willEvictObject),該方法中不能修改緩存中內容。
2、AutoPurgeCache的設計
具體代碼如下:
@interface AutoPurgeCache : NSCache
@end
@implementation AutoPurgeCache
- (nonnull instancetype)init {
self = [super init];
if (self) {
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
return self;
}
- (void)dealloc {
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
@end
說明:因為NSCache在iOS7系統中不會響應內存告警,SDWebImage中就子類化了NSCache,自己監聽內存告警,并removeAllObjects。
3、內存緩存的清除策略
當內存不足,收到內存警告(UIApplicationDidReceiveMemoryWarningNotification)時,會清除內存緩存中所有數據(100%實現)。
內存緩存可以設置countLimit和 totalCostLimit(默認沒設置);當緩存對象個數超過countLimit,或者cost之和超過totalCostLimit,cache中的對象會觸發清除機制,但是具體的清除時機和策略依賴于cache的實現細節。
三、磁盤緩存圖片
1、保存圖片到磁盤
將 UIImage 保存到磁盤的方式有三種:1)直接用 NSKeyedArchiver 把 UIImage 序列化保存; 2)用 UIImagePNGRepresentation() 先把圖片轉為 PNG 保存; 3) **UIImageJPEGRepresentation() **把圖片壓縮成 JPEG 保存。
其中,NSKeyedArchiver 是調用了 UIImagePNGRepresentation() 進行序列化的,用它來保存圖片是消耗最大的。蘋果對 JPEG 有硬編碼和硬解碼,保存成 JPEG 會大大縮減編碼解碼時間,也能減小文件體積。如果圖片不包含透明像素時,**UIImageJPEGRepresentation(0.9) **是比較好的圖片保存方式,其次是 UIImagePNGRepresentation()。
從網絡中下載下來的是圖片數據(imageData), 只有在SDWebImageDownloaderScaleDownLargeImages情形下,圖片壓縮后,使用UIImagePNGRepresentation()進行序列化,更新imageData,再保存到磁盤,其他情況下,直接保存網絡下載下來的imageData到磁盤。
2、從磁盤中讀取圖片
當SDWebImageManager向SDImageCache請求圖片資源時,內存緩存中沒有圖片,需要從磁盤中讀取圖片數據(diskImageDataBySearchingAllPathsForKey:),然后解碼圖片,并將解碼結果保存在內存緩存中;如果內存和磁盤中都沒有,才去網絡請求數下載圖片。
-
imageData轉成image對象的工作由UIImage (MultiFormat)中的sd_imageWithData:完成,其中利用sd_imageOrientationFromImageData:來根據imageData數據獲取圖片類型。利用sd_imageOrientationFromImageData:獲取圖片的旋轉方向。
+ (nullable UIImage *)sd_imageWithData:(nullable NSData *)data { if (!data) { return nil; } UIImage *image; SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:data]; if (imageFormat == SDImageFormatGIF) { image = [UIImage sd_animatedGIFWithData:data]; } #ifdef SD_WEBP else if (imageFormat == SDImageFormatWebP) { image = [UIImage sd_imageWithWebPData:data]; } #endif else { image = [[UIImage alloc] initWithData:data]; #if SD_UIKIT || SD_WATCH UIImageOrientation orientation = [self sd_imageOrientationFromImageData:data]; if (orientation != UIImageOrientationUp) { image = [UIImage imageWithCGImage:image.CGImage scale:image.scale orientation:orientation]; } #endif } return image; }
3、磁盤緩存的清除策略
每次下載下來的圖片數據,默認會保存在磁盤中;時間久了,如果不去清除就的圖片數據,就會浪費磁盤空間,所以SDWebImage在 App退出到后臺、應用即將被終止 的時候會執行清除工作。主要是清除所有過期(保存時間超過maxCacheAge)的圖片,如果還未到達磁盤空間要求(大于maxCacheSize),再從舊到新刪除文件,直到滿足。最大緩存時間(maxCacheAge)和最大占用磁盤空間(maxCacheSize)在 SDImageCacheConfig類設置。
-
App退出到后臺清除緩存需要注意:此時需要調用UIApplication的beginBackgroundTaskWithExpirationHandler:方法,來向iOS系統借點時間,處理清除過期圖片這個耗時較長的任務,在任務結束后需要調用endBackgroundTask:方法,并標志任務結束,否則程序會被iOS系統終止。
- (void)backgroundDeleteOldFiles { Class UIApplicationClass = NSClassFromString(@"UIApplication"); if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) { return; } UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)]; __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{ // Clean up any unfinished task business by marking where you // stopped or ending the task outright. [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }]; // Start the long-running task and return immediately. [self deleteOldFilesWithCompletionBlock:^{ [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }]; }
End
-
參考資料
我是南華coder,曾經夢想寫詩走天涯,而今埋頭苦思忙coder。