官方SDWebImage的架構(gòu)圖
SDWebImage庫(kù)的作用:
通過(guò)對(duì)UIImageView的類(lèi)別擴(kuò)展來(lái)實(shí)現(xiàn)異步加載替換圖片的工作大莫。
主要用到的對(duì)象:
- UIImageView (WebCache)類(lèi)別擂啥,入口封裝侨把,實(shí)現(xiàn)讀取圖片完成后的回調(diào)
- SDWebImageManager痹扇,對(duì)圖片進(jìn)行管理的中轉(zhuǎn)站,記錄哪些圖片正在讀取
(1)向下層讀取Cache(調(diào)用SDImageCache)噩凹,或者向網(wǎng)絡(luò)讀取對(duì)象(調(diào)用SDWebImageDownloader)临扮。 (2)實(shí)現(xiàn)SDImageCache和SDWebImageDownloader的回調(diào) - SDImageCache
(1)根據(jù)URL的MD5摘要對(duì)圖片進(jìn)行存儲(chǔ)和讀取(實(shí)現(xiàn)存在內(nèi)存中或者存在硬盤(pán)上兩種實(shí)現(xiàn))
(2)實(shí)現(xiàn)圖片和內(nèi)存清理工作 - SDWebImageDownloader剃允,根據(jù)URL向網(wǎng)絡(luò)讀取數(shù)據(jù)(實(shí)現(xiàn)部分讀取和全部讀取后再通知回調(diào)兩種方式)
SDWebImage 緩存流程
以最為常用的UIImageView為例:
- UIImageView+WebCache: setImageWithURL:placeholderImage:options: 先顯示 placeholderImage 沛简,同時(shí)由SDWebImageManager 根據(jù) URL 來(lái)在本地查找圖片齐鲤。
- SDWebImageManager: downloadWithURL:delegate:options:userInfo: SDWebImageManager是將UIImageView+WebCache同SDImageCache鏈接起來(lái)的類(lèi), SDImageCache: queryDiskCacheForKey:delegate:userInfo:用來(lái)根據(jù)CacheKey查找圖片是否已經(jīng)在緩存中
- 如果內(nèi)存中已經(jīng)有圖片緩存椒楣, SDWebImageManager會(huì)回調(diào)SDImageCacheDelegate : imageCache:didFindImage:forKey:userInfo:
- 而 UIImageView+WebCache 則回調(diào)SDWebImageManagerDelegate: webImageManager:didFinishWithImage:來(lái)顯示圖片给郊。
- 如果內(nèi)存中沒(méi)有圖片緩存,那么生成 NSInvocationOperation 添加到隊(duì)列捧灰,從硬盤(pán)查找圖片是否已被下載緩存淆九。
- 根據(jù) URLKey 在硬盤(pán)緩存目錄下嘗試讀取圖片文件。這一步是在 NSOperation 進(jìn)行的操作毛俏,所以回主線(xiàn)程進(jìn)行結(jié)果回調(diào) notifyDelegate:炭庙。
- 如果上一操作從硬盤(pán)讀取到了圖片,將圖片添加到內(nèi)存緩存中(如果空閑內(nèi)存過(guò)小煌寇,會(huì)先清空內(nèi)存緩存)焕蹄。SDImageCacheDelegate 回調(diào) imageCache:didFindImage:forKey:userInfo:。進(jìn)而回調(diào)展示圖片阀溶。
- 如果從硬盤(pán)緩存目錄讀取不到圖片腻脏,說(shuō)明所有緩存都不存在該圖片,需要下載圖片银锻,回調(diào) imageCache:didNotFindImageForKey:userInfo:永品。
- 共享或重新生成一個(gè)下載器 SDWebImageDownloader 開(kāi)始下載圖片。
- 圖片下載由 NSURLSession 來(lái)做击纬,實(shí)現(xiàn)相關(guān) delegate 來(lái)判斷圖片下載中鼎姐、下載完成和下載失敗。
- connection:didReceiveData: 中利用 ImageIO 做了按圖片下載進(jìn)度加載效果更振。
- connectionDidFinishLoading: 數(shù)據(jù)下載完成后交給 SDWebImageDecoder 做圖片解碼處理症见。
- 圖片解碼處理在一個(gè) NSOperationQueue 完成,不會(huì)拖慢主線(xiàn)程 UI殃饿。如果有需要對(duì)下載的圖片進(jìn)行二次處理,最好也在這里完成芋肠,效率會(huì)好很多乎芳。
- 在主線(xiàn)程 notifyDelegateOnMainThreadWithInfo: 宣告解碼完成,imageDecoder:didFinishDecodingImage:userInfo: 回調(diào)給 SDWebImageDownloader帖池。
- imageDownloader:didFinishWithImage: 回調(diào)給 SDWebImageManager 告知圖片下載完成奈惑。
- 通知所有的 downloadDelegates 下載完成,回調(diào)給需要的地方展示圖片睡汹。
- 將圖片保存到 SDImageCache 中肴甸,內(nèi)存緩存和硬盤(pán)緩存同時(shí)保存。
- 寫(xiě)文件到硬盤(pán)在單獨(dú) NSInvocationOperation 中完成囚巴,避免拖慢主線(xiàn)程原在。
- 如果是在iOS上運(yùn)行友扰,SDImageCache 在初始化的時(shí)候會(huì)注冊(cè)notification 到UIApplicationDidReceiveMemoryWarningNotification 以及 UIApplicationWillTerminateNotification,在內(nèi)存警告的時(shí)候清理內(nèi)存圖片緩存,應(yīng)用結(jié)束的時(shí)候清理過(guò)期圖片庶柿。
- SDWebImagePrefetcher 可以預(yù)先下載圖片村怪,方便后續(xù)使用。
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]; } } 復(fù)制代碼
-
清除緩存
- (void)clearDisk { NSLog(@"SDWebImageCache---%@", [self readSDWebImageCache]); [[SDImageCache sharedImageCache] clearDiskOnCompletion:nil]; [[SDImageCache sharedImageCache] clearMemory]; //可不寫(xiě) NSLog(@"SDWebImageCache2---%@", [self readSDWebImageCache]); } 復(fù)制代碼
SDWebImage 緩存
-
清理緩存圖片的策略:
特別是最大緩存空間大小的設(shè)置浮庐。如果所有緩存文件的總大小超過(guò)這一大小甚负,則會(huì)按照文件最后修改時(shí)間的逆序,以每次一半的遞歸來(lái)移除那些過(guò)早的文件审残,直到緩存的實(shí)際大小小于我們?cè)O(shè)置的最大使用空間梭域。
注意:它默認(rèn)只支持超過(guò)7天的圖片清除。不對(duì)圖片緩存大小進(jìn)行控制搅轿。當(dāng)然它已經(jīng)做了這種機(jī)制病涨,只是maxCacheSize為默認(rèn)值0,所以不生效介时。- 遍歷緩存目錄使用下面函數(shù)
NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey]; NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL includingPropertiesForKeys:resourceKeys options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:NULL]; 復(fù)制代碼
- 歸檔過(guò)期緩存
for (NSURL *fileURL in fileEnumerator) { ...... // 根據(jù)文件路徑最后修改時(shí)間來(lái)獲取內(nèi)容 NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey]; // 判斷是否過(guò)緩存期 if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) { [urlsToDelete addObject:fileURL]; continue; } // 這里同時(shí)對(duì)未過(guò)期的文件根據(jù)文件大小進(jìn)行歸檔没宾,便以后續(xù)重置緩存. NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; currentCacheSize += [totalAllocatedSize unsignedIntegerValue]; [cacheFiles setObject:resourceValues forKey:fileURL]; } 復(fù)制代碼
- 刪除過(guò)期緩存
for (NSURL *fileURL in urlsToDelete) { [_fileManager removeItemAtURL:fileURL error:nil]; } 復(fù)制代碼
- 重置緩存大小
// 依據(jù)文件修改時(shí)間,對(duì)未過(guò)期的文件進(jìn)行升序排序. NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent usingComparator:^NSComparisonResult(id obj1, id obj2) { return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]]; }]; // 根據(jù)設(shè)定的緩存大小沸柔,對(duì)當(dāng)前緩存進(jìn)行調(diào)整循衰,刪除那些快過(guò)期的文件,使當(dāng)前總的文件大小小與設(shè)定的緩存大小褐澎。 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; } } } 復(fù)制代碼
-
app事件注冊(cè)使用經(jīng)典的觀(guān)察者模式会钝,當(dāng)觀(guān)察到內(nèi)存警告、程序被終止工三、程序進(jìn)入后臺(tái)這些事件時(shí)迁酸,程序?qū)⒆詣?dòng)調(diào)用相應(yīng)的方法處理
當(dāng)收到系統(tǒng)內(nèi)存告警通知時(shí),對(duì)內(nèi)存緩存進(jìn)行處理
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(clearMemory) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; 復(fù)制代碼
當(dāng)進(jìn)程終止時(shí)俭正,對(duì)緩存文件進(jìn)行處理
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanDisk) name:UIApplicationWillTerminateNotification object:nil]; 復(fù)制代碼
當(dāng)進(jìn)入后臺(tái)運(yùn)行時(shí)奸鬓,對(duì)緩存文件進(jìn)行處理
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundCleanDisk) name:UIApplicationDidEnterBackgroundNotification object:nil]; 復(fù)制代碼
SDWebImage 源碼解析
SDImageDownloader負(fù)責(zé)管理所有的下載任務(wù),具體的下載任務(wù)由SDImageDownloaderOperation類(lèi)負(fù)責(zé)掸读。
-
SDWebImageDownloaderOperation
開(kāi)發(fā)者就可以不使用SDWebImage提供的下載任務(wù)類(lèi)串远,而可以自定義相關(guān)類(lèi),只需要遵守協(xié)議即可儿惫,SDWebImageDownloaderOperation類(lèi)也遵守了該協(xié)議澡罚,該類(lèi)繼承自 NSOperation 主要是為了將任務(wù)加進(jìn)并發(fā)隊(duì)列里實(shí)現(xiàn)多線(xiàn)程下載多張圖片,真正實(shí)現(xiàn)下載操作的是 NSURLSessionTask 類(lèi)的子類(lèi)肾请,這里就可以看出 SDWebImage 使用 NSURLSession 實(shí)現(xiàn)下載圖片的功能
-
SDImageDownloader
SDWebImage主要使用了自定義NSOperation子類(lèi)留搔,并在這個(gè)自定義NSOperation子類(lèi)中通過(guò)一個(gè)可用的NSURLSession來(lái)創(chuàng)建一個(gè)執(zhí)行服務(wù)器交互數(shù)據(jù)的NSURLSessionDataTask的下載任務(wù),并由其全權(quán)負(fù)責(zé)下載工作铛铁,接著使用NSOperationQueue實(shí)現(xiàn)多線(xiàn)程的多圖片下載隔显。
其他
常見(jiàn)SDWebImageOptions
SDWebImageRetryFailed, 下載失敗后會(huì)自動(dòng)重新下載
SDWebImageLowPriority, 當(dāng)正在與UI進(jìn)行交互時(shí)却妨,自動(dòng)暫停內(nèi)部的一些下載功能
SDWebImageRetryFailed | SDWebImageLowPriority,同時(shí)存在上邊兩種
SDWebImageCacheMemoryOnly荣月, 取消磁盤(pán)緩存只有內(nèi)存緩存
SDWebImageProgressiveDownload管呵,默認(rèn)情況,圖像會(huì)在下載完成后一次性顯示默認(rèn)存儲(chǔ)法都是是內(nèi)存緩存和磁盤(pán)緩存結(jié)合的方式哺窄。如果你只需要內(nèi)存緩存捐下,那么在帶options選項(xiàng)的方法options這里選擇SDWebImageCacheMemoryOnly就可以了
對(duì)于圖片的緩存實(shí)際應(yīng)用的是NSURLCache自帶的cache機(jī)制。NSURLCache每次都要把緩存的raw data 再轉(zhuǎn)化為UIImage
SDWebImage提供了如下三個(gè)category來(lái)進(jìn)行緩存
MKAnnotationView(WebCache)
UIButton(WebCache)
UIImageView(WebCache)-
比如在下載某個(gè)圖片的過(guò)程中要響應(yīng)一個(gè)事件萌业,就覆蓋這個(gè)方法:
[[SDWebImageManager sharedManager].imageDownloader downloadImageWithURL:urlPath options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) { NSLog(@"下載進(jìn)度---%f", (float)receivedSize/expectedSize); } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) { NSLog(@"下載完成---%@", [NSThread currentThread]); }]; 復(fù)制代碼
圖片下載速度不一致坷襟,用戶(hù)快速滾動(dòng)的時(shí)候,會(huì)因?yàn)閏ell重用導(dǎo)致圖片混亂
解決辦法:MVC生年,使用模型保持下載的圖像婴程,再次刷新表格。將圖像保存到模型里的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):不用重復(fù)下載抱婉,利用MVC刷新表格档叔,不會(huì)造成數(shù)據(jù)混亂,加載速度比較快
缺點(diǎn):內(nèi)存蒸绩。所有下載好的圖像衙四,都會(huì)記錄在模型里。如果數(shù)據(jù)比較多(2000)造成內(nèi)存警告圖片格式簡(jiǎn)介
PNG:無(wú)損壓縮患亿,壓縮比較低传蹈,PNG圖片一般會(huì)比JPG大。(GPU解壓縮的消耗非常小步藕,解壓縮的速度比較快惦界,比較清晰,蘋(píng)果推薦使用)
JPG:有損壓縮咙冗!壓縮比非常高沾歪!照相機(jī)使用(GPU解壓縮的消耗非常大)
GIF:動(dòng)圖
BMP:位圖,沒(méi)有任何壓縮雾消,幾乎不用SDWebImage自己的編解碼技術(shù)
在展示一張圖片的時(shí)候常使用imageNamed:這樣的類(lèi)方法去獲取并展示這張圖片瞬逊,但是圖片是以二進(jìn)制的格式保存在磁盤(pán)或內(nèi)存中的,如果要展示一張圖片需要根據(jù)圖片的不同格式去解碼為正確的位圖交由系統(tǒng)控件來(lái)展示仪或,而解碼的操作默認(rèn)是放在主線(xiàn)程執(zhí)行,凡是放在主線(xiàn)程執(zhí)行的任務(wù)都務(wù)必需要考慮清楚士骤,如果有大量圖片要展示范删,就會(huì)在主線(xiàn)程中執(zhí)行大量的解碼任務(wù),勢(shì)必會(huì)阻塞主線(xiàn)程造成卡頓拷肌,所以SDWebImage自己實(shí)現(xiàn)相關(guān)的編解碼操作到旦,并在子線(xiàn)程中處理旨巷,就不會(huì)影響主線(xiàn)程的相關(guān)操作