一、SDWebImage的核心是:SDWebImageManger。SDWebImage的工作就是由它調(diào)度SDImageCache(一個處理緩存的類)和一個SDWebImageDownloader(負責下載網(wǎng)絡圖片)來完成的馍悟。
二、SDWebImage提供了如下三個category來進行緩存丈钙。
1.UIImageView + WebCache imageView的圖片
2.UIButton + WebCache 給按鈕設置圖片
3.MKAnnotationView + WebCache 地圖大頭針
1.首先將placeholderImage進行展示,SDWebImageManager根據(jù)URL開始處理圖片
2.SDImageCache從緩存中查找圖片圖片,如果有SDImageCacheDelegate回調(diào)image:didFindImage:forkey:useInfo:給SDWebImageManager ,SDWebImageManagerDelegate 回調(diào) webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示圖片
3.緩存中沒有溶锭,生成NSInvocationOperation添加到隊列中開始在硬盤中查找,
根據(jù) URLKey 在硬盤緩存目錄下嘗試讀取圖片文件梳庆。這一步是在 NSOperation 進行的操作暖途,所以回主線程進行結(jié)果回調(diào) notifyDelegate:。
如果找到會將圖片添加到內(nèi)存緩存中(如果空閑緩存不夠膏执,會先清理)然后SDImageCacheDelegate回調(diào)imageCache:didFindImage:forKey:userInfo:。進而回調(diào)展示圖片露久。
4.如果硬盤中沒有則共享或生成下載器SDWebImageDownLoader開始下載圖片更米,圖片下載由NSURLConnection來做,實現(xiàn)相關(guān) delegate 來判斷圖片下載中毫痕、下載完成和下載失敗征峦。
connection:didReceiveData: 中利用 ImageIO 做了按圖片下載進度加載效果。
connectionDidFinishLoading: 數(shù)據(jù)下載完成后交給 SDWebImageDecoder 做圖片解碼處理消请。
5.圖片解碼處理在一個NSOperationQueue完成栏笆,不會拖慢主線程UI。如果有需要對下載的圖片進行二次處理臊泰,最好也在這里完成蛉加,效率會好很多。
6.在主線程notifyDelegateOnMainThreadWithInfo: 宣告解碼完成缸逃,imageDecoder:didFinishDecodingImage:userInfo: 回調(diào)給SDWebImageDownloader针饥。
imageDownloader:didFinishWithImage: 回調(diào)給SDWebImageManager告知圖片下載完成
7.通知所有的downloadDelegates下載完成,回調(diào)給需要的地方展示圖片需频。將圖片保存到SDImageCache中丁眼,內(nèi)存緩存和硬盤緩存同時保存。寫文件到硬盤也在以單獨NSInvocationOperation完成昭殉,避免拖慢主線程苞七。
8.SDImageCache在初始化的時候會注冊一些消息通知藐守,在內(nèi)存警告或退到后臺的時候清理內(nèi)存圖片緩存,應用結(jié)束的時候清理過期圖片蹂风,
當應用進入后臺時吗伤,會涉及到『Long-Running Task』正常程序在進入后臺后、雖然可以繼續(xù)執(zhí)行任務硫眨。但是在時間很短內(nèi)就會被掛起待機足淆。
Long-Running可以讓系統(tǒng)為app再多分配一些時間來處理一些耗時任務。
流程圖如下
四礁阁、緩存要點
1.SDWebImage 實現(xiàn)了一個叫做 AutoPurgeCache 的類 繼承自 NSCache 巧号,相比于普通的 NSCache, 它提供了一個在內(nèi)存緊張時候釋放緩存的能力姥闭。
自動刪除機制:當系統(tǒng)內(nèi)存緊張時丹鸿,NSCache 會自動刪除一些緩存對象
線程安全:從不同線程中對同一個 NSCache 對象進行增刪改查時,不需要加鎖
不同于 NSMutableDictionary棚品、NSCache存儲對象時不會對 key 進行 copy 操作
@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
2.通過內(nèi)部的一個枚舉可以看出磁盤不是強制寫入的
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { /**
* 禁用磁盤緩存
*/ SDWebImageCacheMemoryOnly = 1 << 2,
}
3.緩存的時機有兩種情況:一個是在下載完成之后,自動保存铜跑,或者開發(fā)者通過代理處理完圖片并返回后緩存门怪。二是當緩存中沒有、但是從硬盤中查詢到了圖片锅纺。
4.磁盤的緩存時長默認是一周掷空,清除時間是當程序退出到后臺、或者被殺死的時候囤锉,在后臺執(zhí)行耗時任務是要申請時間坦弟,不要一進入后臺短時間就被掛起
// 默認緩存時長
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
// 清理時間
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deleteOldFiles)
name:UIApplicationWillTerminateNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundDeleteOldFiles)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
/* 磁盤清理的原則
首先、通過時間進行清理官地。(最后修改時間>一周)
然后酿傍、根據(jù)占據(jù)內(nèi)存大小進行清理。(如果占據(jù)內(nèi)存大于上限驱入、則按時間排序赤炒、刪除到上限的1/2。)
由于在源碼中沒有找到給maxCacheSize設置最大顯示的代碼沧侥,所以猜測默認沒有設置上限
*/
// 清理磁盤的方法
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;
5.如何保證內(nèi)存和磁盤的讀寫安全可霎?
a:NScache是線程安全的,在多線程操作中宴杀,不需要對Cache加鎖癣朗。
讀取緩存的時候是在主線程進行。所有不需要要擔心線程安全
b:磁盤的讀取雖然創(chuàng)建了一個NSOperation對象旺罢、但據(jù)我所見這個對象只是用來標記該操作是否被取消旷余、以及取消之后不再讀取磁盤文件的作用绢记。
真正的磁盤緩存是在另一個IO專屬線程中的一個串行隊列下進行的。
如果你搜索self.ioQueue還能發(fā)現(xiàn)正卧、不只是讀取磁盤內(nèi)容蠢熄。
包括刪除、寫入等所有磁盤內(nèi)容都是在這個IO線程進行炉旷、以保證線程安全签孔。
但計算大小、獲取文件總數(shù)等操作窘行。則是在主線程進行饥追。
- (void)checkIfQueueIsIOQueue {
const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);
if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
NSLog(@"This method should be called from the ioQueue");
}
}
6.磁盤的路徑:默認路徑
緩存在磁盤沙盒目錄下Library/Caches
二級目錄為~/Library/Caches/default/com.hackemist.SDWebImageCache.default
當然也可自定義文件名
- 下載最大并發(fā)數(shù)、超時時長罐盔?
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 6;
_downloadTimeout = 15.0;
8但绕、緩存圖片命名
//1.寫入緩存時、直接用圖片url作為key
//寫入緩存 NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost]
// 2.寫入磁盤用url的MD5編碼作為key惶看∧笏常可以防止文件名過長,以及避免重名
五纬黎、圖片格式和解碼
1.為什么下載和從磁盤中讀取的圖片要解碼?
一般下載或者從磁盤獲取的圖片是PNG或者JPG幅骄,這是經(jīng)過編碼壓縮后的圖片數(shù)據(jù),不是位圖莹桅,要把它們渲染到屏幕前就需要進行解碼轉(zhuǎn)成位圖數(shù)據(jù)昌执,而這個解碼操作比較耗時。
你也可以這么理解诈泼,圖片在遠端存儲一定都是編碼后存儲的,這樣體積小煤禽,一個圖像可以看做是一個圖像文件铐达,里面包含了文件頭,文件體和文件尾檬果,圖像的數(shù)據(jù)就包含在文件體中瓮孙,而我們的解碼就是運用算法將文件體中的圖像數(shù)據(jù)轉(zhuǎn)化為位圖數(shù)據(jù),方便渲染和展示选脊。
iOS默認是在主線程解碼杭抠,所以SDWebImage將這個過程放到子線程了。
同時因為位圖體積很大恳啥,所以磁盤緩存不會直接緩存位圖數(shù)據(jù)偏灿,而是編碼壓縮后的PNG或JPG數(shù)據(jù)。
2.怎么判斷圖片格式钝的?
將數(shù)據(jù)data轉(zhuǎn)為十六進制數(shù)據(jù)翁垂,取第一個字節(jié)數(shù)據(jù)進行判斷铆遭。
3.如何播放gif圖片
3.1 把用戶傳入的gif圖片轉(zhuǎn)成NSData
3.2 根據(jù)該Data創(chuàng)建一個圖片數(shù)據(jù)源(NSData->CFImageSourceRef)
3.3 計算該數(shù)據(jù)源中一共有多少幀,把每一幀數(shù)據(jù)取出來放到圖片數(shù)組中
3.4 根據(jù)得到的數(shù)組+計算的動畫時間
3.5 [UIImage animatedImageWithImages:images duration:duration];
六沿猜、SDWebImage在多線程下載圖片時防止錯亂的策略
由于cell的重用機制枚荣,在我們加載出一個cell的時候imageView數(shù)據(jù)源開啟一個下載任務并返回一個image,當cell重用時啼肩,其數(shù)據(jù)源又會開啟一個下載任務下載新的image,但關(guān)聯(lián)的對象是同一個imageView橄妆,這個時候直接setImage時會發(fā)生錯亂。
SDWebImage的處理是:
imageView對象會關(guān)聯(lián)一個下載列表(列表是給AnimationImages用的祈坠,這個時候會下載多張圖片)害碾,當tableview滑動,imageView重設數(shù)據(jù)源(url)時颁虐,會cancel掉下載列表中所有的任務蛮原,然后開啟一個新的下載任務。這樣子就保證了只有當前可見的cell對象的imageView對象關(guān)聯(lián)的下載任務能夠回調(diào)另绩,不會發(fā)生image錯亂儒陨。
同時,SDWebImage管理了一個全局下載隊列(在DownloadManager中),并發(fā)量設置為6.也就是說如果可見cell的數(shù)目是大于6的笋籽,就會有部分下載隊列處于等待狀態(tài)蹦漠。而且,在添加下載任務到全局的下載隊列中去的時候车海,SDWebImage默認是采取LIFO(last in,first out)策略的笛园,具體是在添加下載任務的時候,將上次添加的下載任務添加依賴為新添加的下載任務侍芝。
[wself.downloadQueue addOperation:operation];
if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[wself.lastAddedOperationaddDependency:operation];
wself.lastAddedOperation = operation;
}
七研铆、下載圖片失敗后的處理
使用
- (void)sd_setImageWithURL:(NSURL *)url
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder
這兩個方法下載圖片,如果下載第一次 失敗了州叠,再使用同樣的url 調(diào)用該方法棵红,也不會進行第二次嘗試, 因為 SD會記錄 失敗的URL 咧栗,對它直接進行錯誤處理,
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
好在 SD 有提供接口 靈活 處理這樣的情況逆甜,避免這樣的情況 是使用
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
SDWebImageRetryFailed = 1 << 0, // 值為2的0次方
SDWebImageLowPriority = 1 << 1, // 值為2的1次方
SDWebImageCacheMemoryOnly = 1 << 2, // 值為2的2次方
SDWebImageProgressiveDownload = 1 << 3, // 值為2的3次方
SDWebImageRefreshCached = 1 << 4, // 值為2的4次方
SDWebImageContinueInBackground = 1 << 5, // 值為2的5次方
SDWebImageHandleCookies = 1 << 6, // 值為2的6次方
SDWebImageAllowInvalidSSLCertificates = 1 << 7, // 值為2的7次方
SDWebImageHighPriority = 1 << 8,
SDWebImageDelayPlaceholder = 1 << 9,
SDWebImageTransformAnimatedImage = 1 << 10,
SDWebImageAvoidAutoSetImage = 1 << 11,
SDWebImageScaleDownLargeImages = 1 << 12
};
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options
// 由于SD提供的這個枚舉是位移枚舉,可以同時傳入多個致板,比如:
[icommageView sd_setImageWithURL:url placeholderImage:image options:SDWebImageRefreshCached | SDWebImageRetryFailed progress:nil completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
}];
注:本文是通過個人研讀SD源碼和參考其他博文總結(jié)的SD實現(xiàn)流程和使用過程中有可能遇到的問題交煞,如有大佬發(fā)現(xiàn)有誤之處,歡迎指正??