官方SDWebImage的架構圖
SDWebImage庫的作用:
通過對UIImageView的類別擴展來實現異步加載替換圖片的工作。
主要用到的對象:
- UIImageView (WebCache)類別,入口封裝,實現讀取圖片完成后的回調
- SDWebImageManager,對圖片進行管理的中轉站,記錄哪些圖片正在讀取
(1)向下層讀取Cache(調用SDImageCache),或者向網絡讀取對象(調用SDWebImageDownloader)。 (2)實現SDImageCache和SDWebImageDownloader的回調 - SDImageCache
(1)根據URL的MD5摘要對圖片進行存儲和讀取(實現存在內存中或者存在硬盤上兩種實現)
(2)實現圖片和內存清理工作 - SDWebImageDownloader,根據URL向網絡讀取數據(實現部分讀取和全部讀取后再通知回調兩種方式)
SDWebImage 緩存流程
以最為常用的UIImageView為例:
- UIImageView+WebCache: setImageWithURL:placeholderImage:options: 先顯示 placeholderImage ,同時由SDWebImageManager 根據 URL 來在本地查找圖片。
- SDWebImageManager: downloadWithURL:delegate:options:userInfo: SDWebImageManager是將UIImageView+WebCache同SDImageCache鏈接起來的類, SDImageCache: queryDiskCacheForKey:delegate:userInfo:用來根據CacheKey查找圖片是否已經在緩存中
- 如果內存中已經有圖片緩存, SDWebImageManager會回調SDImageCacheDelegate : imageCache:didFindImage:forKey:userInfo:
- 而 UIImageView+WebCache 則回調SDWebImageManagerDelegate: webImageManager:didFinishWithImage:來顯示圖片。
- 如果內存中沒有圖片緩存,那么生成 NSInvocationOperation 添加到隊列,從硬盤查找圖片是否已被下載緩存。
- 根據 URLKey 在硬盤緩存目錄下嘗試讀取圖片文件。這一步是在 NSOperation 進行的操作,所以回主線程進行結果回調 notifyDelegate:。
- 如果上一操作從硬盤讀取到了圖片,將圖片添加到內存緩存中(如果空閑內存過小,會先清空內存緩存)。SDImageCacheDelegate 回調 imageCache:didFindImage:forKey:userInfo:。進而回調展示圖片。
- 如果從硬盤緩存目錄讀取不到圖片,說明所有緩存都不存在該圖片,需要下載圖片,回調 imageCache:didNotFindImageForKey:userInfo:。
- 共享或重新生成一個下載器 SDWebImageDownloader 開始下載圖片。
- 圖片下載由 NSURLSession 來做,實現相關 delegate 來判斷圖片下載中、下載完成和下載失敗。
- connection:didReceiveData: 中利用 ImageIO 做了按圖片下載進度加載效果。
- connectionDidFinishLoading: 數據下載完成后交給 SDWebImageDecoder 做圖片解碼處理。
- 圖片解碼處理在一個 NSOperationQueue 完成,不會拖慢主線程 UI。如果有需要對下載的圖片進行二次處理,最好也在這里完成,效率會好很多。
- 在主線程 notifyDelegateOnMainThreadWithInfo: 宣告解碼完成,imageDecoder:didFinishDecodingImage:userInfo: 回調給 SDWebImageDownloader。
- imageDownloader:didFinishWithImage: 回調給 SDWebImageManager 告知圖片下載完成。
- 通知所有的 downloadDelegates 下載完成,回調給需要的地方展示圖片。
- 將圖片保存到 SDImageCache 中,內存緩存和硬盤緩存同時保存。
- 寫文件到硬盤在單獨 NSInvocationOperation 中完成,避免拖慢主線程。
- 如果是在iOS上運行,SDImageCache 在初始化的時候會注冊notification 到UIApplicationDidReceiveMemoryWarningNotification 以及 UIApplicationWillTerminateNotification,在內存警告的時候清理內存圖片緩存,應用結束的時候清理過期圖片。
- SDWebImagePrefetcher 可以預先下載圖片,方便后續使用。
SDWebImage 使用
-
查看緩存大小
- (NSString *)readSDWebImageCache { NSUInteger size = [SDImageCache sharedImageCache].getSize; // 1k = 1024, 1m = 1024k if (size < 1024) { // 小于1k return [NSString stringWithFormat:@"%ldB",(long)size]; }else if (size < 1024 * 1024) { // 小于1m CGFloat aFloat = size/1024; return [NSString stringWithFormat:@"%.0fK",aFloat]; }else if (size < 1024 * 1024 * 1024) { // 小于1G CGFloat aFloat = size/(1024 * 1024); return [NSString stringWithFormat:@"%.1fM",aFloat]; }else { CGFloat aFloat = size/(1024*1024*1024); return [NSString stringWithFormat:@"%.1fG",aFloat]; } } 復制代碼
-
清除緩存
- (void)clearDisk { NSLog(@"SDWebImageCache---%@", [self readSDWebImageCache]); [[SDImageCache sharedImageCache] clearDiskOnCompletion:nil]; [[SDImageCache sharedImageCache] clearMemory]; //可不寫 NSLog(@"SDWebImageCache2---%@", [self readSDWebImageCache]); } 復制代碼
SDWebImage 緩存
-
清理緩存圖片的策略:
特別是最大緩存空間大小的設置。如果所有緩存文件的總大小超過這一大小,則會按照文件最后修改時間的逆序,以每次一半的遞歸來移除那些過早的文件,直到緩存的實際大小小于我們設置的最大使用空間。
注意:它默認只支持超過7天的圖片清除。不對圖片緩存大小進行控制。當然它已經做了這種機制,只是maxCacheSize為默認值0,所以不生效。- 遍歷緩存目錄使用下面函數
NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey]; NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL includingPropertiesForKeys:resourceKeys options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:NULL]; 復制代碼
- 歸檔過期緩存
for (NSURL *fileURL in fileEnumerator) { ...... // 根據文件路徑最后修改時間來獲取內容 NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey]; // 判斷是否過緩存期 if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) { [urlsToDelete addObject:fileURL]; continue; } // 這里同時對未過期的文件根據文件大小進行歸檔,便以后續重置緩存. NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; currentCacheSize += [totalAllocatedSize unsignedIntegerValue]; [cacheFiles setObject:resourceValues forKey:fileURL]; } 復制代碼
- 刪除過期緩存
for (NSURL *fileURL in urlsToDelete) { [_fileManager removeItemAtURL:fileURL error:nil]; } 復制代碼
- 重置緩存大小
// 依據文件修改時間,對未過期的文件進行升序排序. NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent usingComparator:^NSComparisonResult(id obj1, id obj2) { return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]]; }]; // 根據設定的緩存大小,對當前緩存進行調整,刪除那些快過期的文件,使當前總的文件大小小與設定的緩存大小。 for (NSURL *fileURL in sortedFiles) { if ([_fileManager removeItemAtURL:fileURL error:nil]) { NSDictionary *resourceValues = cacheFiles[fileURL]; NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; currentCacheSize -= [totalAllocatedSize unsignedIntegerValue]; if (currentCacheSize < desiredCacheSize) { break; } } } 復制代碼
-
app事件注冊使用經典的觀察者模式,當觀察到內存警告、程序被終止、程序進入后臺這些事件時,程序將自動調用相應的方法處理
當收到系統內存告警通知時,對內存緩存進行處理
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(clearMemory) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; 復制代碼
當進程終止時,對緩存文件進行處理
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanDisk) name:UIApplicationWillTerminateNotification object:nil]; 復制代碼
當進入后臺運行時,對緩存文件進行處理
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundCleanDisk) name:UIApplicationDidEnterBackgroundNotification object:nil]; 復制代碼
SDWebImage 源碼解析
SDImageDownloader負責管理所有的下載任務,具體的下載任務由SDImageDownloaderOperation類負責。
-
SDWebImageDownloaderOperation
開發者就可以不使用SDWebImage提供的下載任務類,而可以自定義相關類,只需要遵守協議即可,SDWebImageDownloaderOperation類也遵守了該協議,該類繼承自 NSOperation 主要是為了將任務加進并發隊列里實現多線程下載多張圖片,真正實現下載操作的是 NSURLSessionTask 類的子類,這里就可以看出 SDWebImage 使用 NSURLSession 實現下載圖片的功能
-
SDImageDownloader
SDWebImage主要使用了自定義NSOperation子類,并在這個自定義NSOperation子類中通過一個可用的NSURLSession來創建一個執行服務器交互數據的NSURLSessionDataTask的下載任務,并由其全權負責下載工作,接著使用NSOperationQueue實現多線程的多圖片下載。
其他
常見SDWebImageOptions
SDWebImageRetryFailed, 下載失敗后會自動重新下載
SDWebImageLowPriority, 當正在與UI進行交互時,自動暫停內部的一些下載功能
SDWebImageRetryFailed | SDWebImageLowPriority,同時存在上邊兩種
SDWebImageCacheMemoryOnly, 取消磁盤緩存只有內存緩存
SDWebImageProgressiveDownload,默認情況,圖像會在下載完成后一次性顯示默認存儲法都是是內存緩存和磁盤緩存結合的方式。如果你只需要內存緩存,那么在帶options選項的方法options這里選擇SDWebImageCacheMemoryOnly就可以了
對于圖片的緩存實際應用的是NSURLCache自帶的cache機制。NSURLCache每次都要把緩存的raw data 再轉化為UIImage
SDWebImage提供了如下三個category來進行緩存
MKAnnotationView(WebCache)
UIButton(WebCache)
UIImageView(WebCache)-
比如在下載某個圖片的過程中要響應一個事件,就覆蓋這個方法:
[[SDWebImageManager sharedManager].imageDownloader downloadImageWithURL:urlPath options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) { NSLog(@"下載進度---%f", (float)receivedSize/expectedSize); } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) { NSLog(@"下載完成---%@", [NSThread currentThread]); }]; 復制代碼
圖片下載速度不一致,用戶快速滾動的時候,會因為cell重用導致圖片混亂
解決辦法:MVC,使用模型保持下載的圖像,再次刷新表格。將圖像保存到模型里的優缺點
優點:不用重復下載,利用MVC刷新表格,不會造成數據混亂,加載速度比較快
缺點:內存。所有下載好的圖像,都會記錄在模型里。如果數據比較多(2000)造成內存警告圖片格式簡介
PNG:無損壓縮,壓縮比較低,PNG圖片一般會比JPG大。(GPU解壓縮的消耗非常小,解壓縮的速度比較快,比較清晰,蘋果推薦使用)
JPG:有損壓縮!壓縮比非常高!照相機使用(GPU解壓縮的消耗非常大)
GIF:動圖
BMP:位圖,沒有任何壓縮,幾乎不用SDWebImage自己的編解碼技術
在展示一張圖片的時候常使用imageNamed:這樣的類方法去獲取并展示這張圖片,但是圖片是以二進制的格式保存在磁盤或內存中的,如果要展示一張圖片需要根據圖片的不同格式去解碼為正確的位圖交由系統控件來展示,而解碼的操作默認是放在主線程執行,凡是放在主線程執行的任務都務必需要考慮清楚,如果有大量圖片要展示,就會在主線程中執行大量的解碼任務,勢必會阻塞主線程造成卡頓,所以SDWebImage自己實現相關的編解碼操作,并在子線程中處理,就不會影響主線程的相關操作