寫(xiě)在前面
SDWebImage是一個(gè)強(qiáng)大的圖片下載庫(kù)缺菌,提供的主要功能有:圖片異步下載铛纬,圖片緩存蹬挺,圖片解碼以及其他確保程序健壯性的功能。其Github地址戳這里澈圈。
SDWebImage Class Diagram UML類(lèi)圖
SDWebImage官方提供了這個(gè)開(kāi)源庫(kù)的類(lèi)圖彬檀。
為了方便閱讀在上面標(biāo)注了不同箭頭的關(guān)系∷才框架下各個(gè)模塊之間的關(guān)系都展示的很清楚了窍帝,就不再一一解釋。舉個(gè)例子拆魏,通常使用到的
UIImageView
的WebCache
分類(lèi)下sd_setImageWithURL()
方法依賴(lài)于UIView
的WebCache
分類(lèi)下的sd_internalSetImageWithURL()
方法的實(shí)現(xiàn)盯桦。而UIView
的WebCache
分類(lèi)又依賴(lài)于SDWebImageManager
模塊慈俯。第一篇文章則是從這個(gè)角度入手分析SDWebImage的下載流程。
SDWebImage Sequence Diagram 流程圖
官方提供的流程圖如下:
清晰明了拥峦。
源碼分析
SDWebImageManager
SDWebImageManager是封裝好的一個(gè)單例贴膘,通過(guò)
+ (nonnull instancetype)sharedManager;
方法獲取。
SDWebImageManager是用于支撐WebCache
分類(lèi)(如UIImageView+WebCache
)實(shí)現(xiàn)的一個(gè)類(lèi)略号,并且連接了SDWebImageDownloader
異步下載器和SDImageCache
緩存模塊刑峡。
因此,在SDWebImageManager中封裝了以下幾個(gè)重要屬性:
//代理
@property (weak, nonatomic, nullable) id <SDWebImageManagerDelegate> delegate;
//緩存類(lèi)
@property (strong, nonatomic, readonly, nullable) SDImageCache *imageCache;
//異步下載器
@property (strong, nonatomic, readonly, nullable) SDWebImageDownloader *imageDownloader;
代理對(duì)象中有兩個(gè)重要方法(但是是@optional的)在下面的代碼中將會(huì)使用到:
/**
* Controls which image should be downloaded when the image is not found in the cache.
*
* @param imageManager The current `SDWebImageManager`
* @param imageURL The url of the image to be downloaded
*
* @return Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied.
*/
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL;
/**
* Allows to transform the image immediately after it has been downloaded and just before to cache it on disk and memory.
* NOTE: This method is called from a global queue in order to not to block the main thread.
*
* @param imageManager The current `SDWebImageManager`
* @param image The image to transform
* @param imageURL The url of the image to transform
*
* @return The transformed image object.
*/
- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;
- 第一個(gè)方法用于詢(xún)問(wèn)代理在查找不到緩存的情況下是否需要根據(jù)url下載圖片玄柠,默認(rèn)返回YES突梦。如果返回NO,即使緩存未命中羽利,也不執(zhí)行下載操作宫患。
- 第二個(gè)方法用于詢(xún)問(wèn)代理是否需要對(duì)下載的圖像進(jìn)行transform操作,然后緩存transform之后的圖片这弧。如果代理實(shí)現(xiàn)了這個(gè)方法娃闲,則需要返回一張圖片。
WebCache
分類(lèi)中的sd_setImageWithURL
最終都會(huì)調(diào)用SDWebImageManager
類(lèi)中的
- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options :(nullable SDWebImageDownloaderProgressBlock) progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock;
進(jìn)行圖片的請(qǐng)求匾浪。
代碼注釋中有對(duì)該方法中傳入的幾個(gè)參數(shù)做出的說(shuō)明皇帮,其中需要注意傳入的SDInternalCompletionBlock
參數(shù)的作用。
/**
* Downloads the image at the given URL if not present in cache or return the cached version otherwise.
*
* @param url The URL to the image
* @param options A mask to specify options to use for this request
* @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed.
*
* This parameter is required.
*
* This block has no return value and takes the requested UIImage as first parameter and the NSData representation as second parameter.
* In case of error the image parameter is nil and the third parameter may contain an NSError.
*
* The forth parameter is an `SDImageCacheType` enum indicating if the image was retrieved from the local cache
* or from the memory cache or from the network.
*
* The fifth parameter is set to NO when the SDWebImageProgressiveDownload option is used and the image is
* downloading. This block is thus called repeatedly with a partial image. When image is fully downloaded, the
* block is called a last time with the full image and the last parameter set to YES.
*
* The last parameter is the original image URL
*
* @return Returns an NSObject conforming to SDWebImageOperation. Should be an instance of SDWebImageDownloaderOperation
*/
SDWebImageOptions
是一個(gè)枚舉類(lèi)型蛋辈,里面存放了一些用戶(hù)可以自定義的圖片下載/緩存選項(xiàng)属拾,在代碼中有用到的話再專(zhuān)門(mén)解釋其含義。
loadImageWithURL()方法
- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options :(nullable SDWebImageDownloaderProgressBlock) progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock{
// 1. 判斷傳入的url合法性
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// 將url設(shè)置為nil繼續(xù)執(zhí)行后續(xù)操作冷溶,防止程序崩潰
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
//初始化operation
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
BOOL isFailedUrl = NO;
//2. 判斷url是否在failedURLs中
if (url) {
@synchronized (self.failedURLs) {// 加了同步鎖渐白,保證集合類(lèi)使用的線程安全
isFailedUrl = [self.failedURLs containsObject:url];
}
}
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
return operation;
}
//將operation添加到數(shù)組中,任務(wù)完成后將被移除(后面會(huì)提到)
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
//3. 根據(jù)url獲取key
NSString *key = [self cacheKeyForURL:url];
//4. 請(qǐng)求緩存
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
if (operation.isCancelled) {
//4.1 操作已取消
[self safelyRemoveOperationFromRunning:operation];
return;
}
//需要更新已在緩存中的圖片 || 未獲取到緩存圖片
if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
//4.2 options 中的 SDWebImageRefreshCached位為1 || 未獲取到緩存圖片
//用于處理來(lái)自同一個(gè)url的圖片挂洛,但是服務(wù)器中的圖片已經(jīng)更新礼预,因此需要強(qiáng)制下載并更新緩存圖片。
if (cachedImage && options & SDWebImageRefreshCached) {
//緩存中已存在舊圖片虏劲,執(zhí)行回調(diào)通知托酸。
[self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}
//繼續(xù)執(zhí)行下載任務(wù)
SDWebImageDownloaderOptions downloaderOptions = 0;
//省略初始化downloaderOptions代碼
if (cachedImage && options & SDWebImageRefreshCached) {
//更新downloaderOptions
// force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
//使用更新的downloaderOptions開(kāi)啟下載圖片任務(wù)
SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
// 如果任務(wù)被取消,什么都不做
} else if (error) {
//error handling 錯(cuò)誤處理
/*執(zhí)行回調(diào)將error傳出并將url放入failedURLs數(shù)組中*/
//代碼省略
}
else {
if ((options & SDWebImageRetryFailed)) {
/*options的SDWebImageRetryFailed位為1*/
/*默認(rèn)情況下當(dāng)url無(wú)法下載時(shí)柒巫,會(huì)添加到failedURLs數(shù)組中防止反復(fù)嘗試通過(guò)該url下載圖片励堡,而如果該位為1,則該機(jī)制失效堡掏。*/
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
//是否需要存儲(chǔ)在磁盤(pán)上
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
//圖片刷新命中NSURLCache的情況 && downloadedImage為空
} else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
//未命中NSURLCache且downloadedImage不為空
//options的SDWebImageTransformAnimatedImage位為1情況
//此時(shí)說(shuō)明圖片需要執(zhí)行transform操作
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//Allows to transform the image immediately after it has been downloaded and just before to cache it on disk and memory.
//此時(shí)調(diào)用代理方法獲取transformed image
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
// pass nil if the image was transformed, so we can recalculate the data from the image
//緩存圖片
[self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
}
//執(zhí)行回調(diào)
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
});
} else {
if (downloadedImage && finished) {
//一般情況:即以上出現(xiàn)的options位為0且獲取到下載的圖片
//根據(jù)cacheOnDisk緩存downloadedImage
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
}
//執(zhí)行回調(diào)
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
if (finished) {
//通過(guò)原子操作移除下載
[self safelyRemoveOperationFromRunning:strongOperation];
}
}];
//給cancelBlock輔助应结,注意這段代碼不是在這里執(zhí)行的
operation.cancelBlock = ^{
[self.imageDownloader cancel:subOperationToken];
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self safelyRemoveOperationFromRunning:strongOperation];
};
}else if (cachedImage) {
//4.3 請(qǐng)求到緩存圖片 && !SDWebImageRefreshCached
//直接回調(diào),移除操作
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
} else {
// 4.4 圖片沒(méi)有在緩存中 && 代理沒(méi)有允許下載操作
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}];
return operation;
}
方法內(nèi)部的注釋詳細(xì)描述了SDWebImageManager的loadImageWithURL
工作流程。概括起來(lái)其實(shí)很簡(jiǎn)單:首先判斷緩存中是否能獲取到圖片鹅龄,如果沒(méi)獲取到則使用異步下載器下載揩慕,然后使用緩存類(lèi)緩存,執(zhí)行回調(diào)返回圖片扮休。如果緩存中能夠獲取到迎卤,執(zhí)行回調(diào)返回緩存中的圖片。
下一篇文章將著重分析SDWebImage的緩存實(shí)現(xiàn)玷坠。