SDWebImage是一個(gè)開源的第三方庫,它提供了UIImageView的分類來實(shí)現(xiàn)從網(wǎng)絡(luò)端下載數(shù)據(jù)并緩存到內(nèi)存和磁盤。
SDWebImage有如下特點(diǎn):
- 提供了UIImageView和UIButton的分類。以支持加載網(wǎng)絡(luò)圖片并緩存。
- 一個(gè)異步的圖片下載器
- 提供異步的內(nèi)存和磁盤緩存,并自動處理緩存過期。
- 后臺圖片解壓縮處理。
- 確保同一個(gè)URL不會被下載多次。
- 確保主線程永遠(yuǎn)不會阻塞。
一.儲備知識
SDWebImage中每一個(gè)下載任務(wù)都是一個(gè)SDWebImageDownloaderOperation,而SDWebImageDownloaderOperation又是繼承自NSOperation,所以每一個(gè)下載任務(wù)對應(yīng)一個(gè)NSOperation。在SDWebImage中使用SDWebImageDownloader來管理
多個(gè)下載任務(wù),在SDWebImageDownloader中有一個(gè)downloadedQueue這個(gè)屬性,這個(gè)屬性是NSOperationQueue類型的,也就是用NSOperationQueue來管理NSOperation。
下面我們就一起學(xué)習(xí)一下NSOperation和NSOperationQueue。
NSOperation和NSOperationQueue
NSOperation是一個(gè)抽象類,用來表示與單個(gè)任務(wù)相關(guān)聯(lián)的代碼和數(shù)據(jù)。
NSOperation類是一個(gè)抽象類,我們不能直接去使用它而是應(yīng)該創(chuàng)建一個(gè)子類來繼承它。雖然它只是一個(gè)抽象類,但是它的基本實(shí)現(xiàn)還是提供了很有價(jià)值的邏輯來確保任務(wù)的安全執(zhí)行。這種原生的邏輯可以讓我們專注于任務(wù)的真正的實(shí)現(xiàn),而不需要用一些膠水代碼去確保這個(gè)任務(wù)能正常工作在其他地方。
我們可以把一個(gè)NSOperation對象加入到一個(gè)operation queue中,讓這個(gè)operation queue去決定什么時(shí)候執(zhí)行這個(gè)operation。當(dāng)使用operation queue去管理operation時(shí),輪到某個(gè)operation執(zhí)行時(shí)實(shí)際是去執(zhí)行這個(gè)operation的start方法,所以我們一個(gè)operation對象實(shí)際要執(zhí)行的任務(wù)應(yīng)該放在start方法里面。如果我們不想使用operation queue,也可以通過手動調(diào)用NSOperation的start方法來執(zhí)行任務(wù)。
我們可以通過添加依賴來確定operation queue中operation的具體執(zhí)行順序。添加依賴和移除依賴可以使用下列的API:
//添加依賴
- (void)addDependency:(NSOperation *)op;
//移除依賴
- (void)removeDependency:(NSOperation *)op;
只要當(dāng)一個(gè)operation對象的所有依賴都執(zhí)行完成的時(shí)候,其才可以變成熟ready狀態(tài),然后才可以被執(zhí)行。如果一個(gè)operation沒有添加依賴,直接加入了operation queue中,那么就會按照加入隊(duì)列的先后順序,當(dāng)這個(gè)operation的前一個(gè)operation執(zhí)行完成以后,其狀態(tài)才會變成ready,才能被執(zhí)行。
NSOperation對象有下列四個(gè)比較重要的狀態(tài):
- isCancelled
- isExecuting
- isFinished
- isReady
其中isExecuting,isFinished,isReady這三種狀態(tài)相當(dāng)于是operation對象的生命周期:
operation生命周期.png
而isCancelled這種狀態(tài)則比較特殊,當(dāng)我們對operation對象調(diào)用- (void)cancel方法時(shí),其isCancelled屬性會被置為YES。這個(gè)時(shí)候要分兩種情況: - operation正在執(zhí)行
也就是說其狀態(tài)現(xiàn)在是isExecuting,調(diào)用- (void)cancel方法后會馬上停止執(zhí)行當(dāng)前任務(wù),并且狀態(tài)變?yōu)?code>isFinished,isCancelled = Yes。 - operation還沒開始執(zhí)行
這個(gè)時(shí)候operation的狀態(tài)其實(shí)是isReady這個(gè)狀態(tài)之前,operation還沒開始執(zhí)行,調(diào)用- (void)cancel方法后會去調(diào)用operation的start方法,在start方法里我們要去處理cancel事件,并設(shè)置isFinished = YES。
調(diào)用cancel方法.png
SDWebImageOptions
在SDWebImage中大量使用了option類型,通過判斷option類型的值來決定下一步應(yīng)該怎么做,所以如果對這些option值一點(diǎn)都不了解的話可能理解起源碼來也會非常難受。SDWebImageOptions是暴露在外的可供使用者使用的option。還有一些option比如SDImageCacheOptions, SDWebImageDownloaderOptions這些都是不暴露給用戶使用的。源碼中是根據(jù)用戶設(shè)置的SDWebImageOptions這個(gè)option來確定另外兩個(gè)option的值。
下面我們來具體看一下SDWebImageOptions


這里我們也可以看到,
SDImageCacheOptions中的三個(gè)選項(xiàng)在SDWebImageOptions中都有對應(yīng)的選項(xiàng)。
-
SDImageCacheQueryDataWhenInMemory對應(yīng)SDWebImageQueryDataWhenInMemory。 -
SDImageCacheQueryDiskSync對應(yīng)SDWebImageQueryDiskSync。 -
SDImageCacheScaleDownLargeImages對應(yīng)SDWebImageScaleDownLargeImages。
SDWebImageDownloaderOptions

SDWebImageDownloaderOptions中所有選項(xiàng)在SDWebImageOptions中也有相對應(yīng)的選項(xiàng),這里不再贅述。
SDImageCacheType
//從緩存中得不到數(shù)據(jù)
SDImageCacheTypeNone,
//從磁盤緩存中得到數(shù)據(jù)
SDImageCacheTypeDisk,
//從內(nèi)存緩存中得到數(shù)據(jù)
SDImageCacheTypeMemory
框架的主要類和一次圖片加載的主要流程
框架的主要類

一次圖片加載的主要流程

針對上圖中一次圖片加載的主要流程,每一步做介紹:
- 1.SDWebImage為UIImagView創(chuàng)建了一個(gè)分類
UIImageView (WebCache),然后UIImageView對象可以調(diào)用這個(gè)分類的方法來下載圖片:
[imageView sd_setImageWithURL:[NSURL URLWithString:@""]];
- 2.
UIImageView (WebCache)的- (void)sd_setImageWithURL:(nullable NSURL *)url方法實(shí)際調(diào)用了UIView (WebCache)的下列方法:
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
- 3.
UIView (WebCache)的上述方法在實(shí)現(xiàn)時(shí)會創(chuàng)建一個(gè)SDWebImageManager的實(shí)例對象,然后調(diào)用其下列方法來加載圖片:
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock;
- 4.在
SDWebImageManager對象的上述方法里,首先會查詢在緩存中有沒有這個(gè)圖片,然后根據(jù)各種option的判斷決定是否要從網(wǎng)絡(luò)端下載。查詢緩存中有沒有是通過調(diào)用SDImageCache對象的實(shí)例方法來實(shí)現(xiàn)的:
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key
options:(SDImageCacheOptions)options
done:(nullable SDCacheQueryCompletedBlock)doneBlock;
- 5.返回緩存查詢的結(jié)果
- 6.如果需要下載圖片,那么調(diào)用
SDWebImageDownloader對象的下列方法進(jìn)行下載:
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
- 7.獲取從網(wǎng)絡(luò)端下載的圖片。
- 8.判斷是否要將下載的圖片進(jìn)行緩存,如果需要,則緩存。
- 9.把通過
SDWebImageManager對象獲取的圖片顯示在UIImageView上。
源碼分析
這一部分我們進(jìn)行詳細(xì)的源碼分析。
首先從SDWebImageManager類的loadImageWithURL:方法看起:
由于代碼比較長,我就采用注釋的方式
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// Invoking this method without a completedBlock is pointless
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
//如果傳進(jìn)來的是一個(gè)NSString,則把NSString轉(zhuǎn)化為NSURL
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
operation.manager = self;
//self.failedURLs是nsurl的黑名單,一般情況下,如果URL在這個(gè)黑名單里,那么就不會嘗試加載這個(gè)圖片,直接返回
BOOL isFailedUrl = NO;
if (url) {
LOCK(self.failedURLsLock);
isFailedUrl = [self.failedURLs containsObject:url];
UNLOCK(self.failedURLsLock);
}
//SDWebImageRetryFailed即即使URL被加入了黑名單,也要嘗試加載這個(gè)URL對應(yīng)的圖片
//如果URL長度為0,或者URL被加入了黑名單并且沒有設(shè)置SDWebImageRetryFailed,那么就直接回調(diào)完成的block
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;
}
LOCK(self.runningOperationsLock);
[self.runningOperations addObject:operation];
UNLOCK(self.runningOperationsLock);
NSString *key = [self cacheKeyForURL:url];
//由于我們在使用API的時(shí)候只設(shè)置SDWebImageOptions,所以這里就是根據(jù)用戶設(shè)置的SDWebImageOptions去設(shè)置SDImageCacheOptions
SDImageCacheOptions cacheOptions = 0;
if (options & SDWebImageQueryDataWhenInMemory) cacheOptions |= SDImageCacheQueryDataWhenInMemory;
if (options & SDWebImageQueryDiskSync) cacheOptions |= SDImageCacheQueryDiskSync;
if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages;
__weak SDWebImageCombinedOperation *weakOperation = operation;
//這里開始調(diào)用SDImageCache對象的queryCacheOperationForKey:方法去緩存中查找有沒有這個(gè)URL對應(yīng)的圖片
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key options:cacheOptions done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
[self safelyRemoveOperationFromRunning:strongOperation];
return;
}
// 判斷我們是否需要從網(wǎng)絡(luò)端下載圖片
//首先檢查沒有設(shè)置只能從緩存中獲取,然后檢查cachedImage = nil或者設(shè)置了要刷新緩存,則需要從網(wǎng)絡(luò)端下載圖片
BOOL shouldDownload = (!(options & SDWebImageFromCacheOnly))
&& (!cachedImage || options & SDWebImageRefreshCached)
&& (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
if (shouldDownload) {
//從緩存中獲取了圖片并且設(shè)置了要刷新緩存這個(gè)option,則要進(jìn)行兩次完成的回調(diào),這是第一次回調(diào)
if (cachedImage && options & SDWebImageRefreshCached) {
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
// AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}
// download if no image or requested to refresh anyway, and download allowed by delegate
//這里是根據(jù)用戶設(shè)置的SDWebImageOptions來手動設(shè)置SDWebImageDownloaderOptions
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
//如果已經(jīng)從緩存中獲取了圖片并且設(shè)置了要刷新緩存
if (cachedImage && options & SDWebImageRefreshCached) {
//這里其實(shí)就是把SDWebImageDownloaderProgressiveDownload這個(gè)option去掉
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
//加上SDWebImageDownloaderIgnoreCachedResponse這個(gè)option,忽略NSURLCache中緩存的response
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
__weak typeof(strongOperation) weakSubOperation = strongOperation;
//l開始進(jìn)行圖片的下載
strongOperation.downloadToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
__strong typeof(weakSubOperation) strongSubOperation = weakSubOperation;
if (!strongSubOperation || strongSubOperation.isCancelled) {
// Do nothing if the operation was cancelled
// See #699 for more details
// if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
} else if (error) {
[self callCompletionBlockForOperation:strongSubOperation completion:completedBlock error:error url:url];
BOOL shouldBlockFailedURL;
// 后面都是判斷在請求失敗的情況下是否應(yīng)該把
if ([self.delegate respondsToSelector:@selector(imageManager:shouldBlockFailedURL:withError:)]) {
shouldBlockFailedURL = [self.delegate imageManager:self shouldBlockFailedURL:url withError:error];
} else {
shouldBlockFailedURL = ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost
&& error.code != NSURLErrorNetworkConnectionLost);
}
if (shouldBlockFailedURL) {
LOCK(self.failedURLsLock);
[self.failedURLs addObject:url];
UNLOCK(self.failedURLsLock);
}
}
else {
//如果設(shè)置了SDWebImageRetryFailed那么就要把URL從黑名單中移除
if ((options & SDWebImageRetryFailed)) {
LOCK(self.failedURLsLock);
[self.failedURLs removeObject:url];
UNLOCK(self.failedURLsLock);
}
//判斷是否應(yīng)該把下載的圖片緩存到磁盤,SDWebImageCacheMemoryOnly這個(gè)option表示只把圖片緩存到內(nèi)存
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
// We've done the scale process in SDWebImageDownloader with the shared manager, this is used for custom manager and avoid extra scale.
if (self != [SDWebImageManager sharedManager] && self.cacheKeyFilter && downloadedImage) {
downloadedImage = [self scaledImageForKey:key image:downloadedImage];
}
if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
} else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@autoreleasepool {
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
NSData *cacheData;
// pass nil if the image was transformed, so we can recalculate the data from the image
if (self.cacheSerializer) {
cacheData = self.cacheSerializer(transformedImage, (imageWasTransformed ? nil : downloadedData), url);
} else {
cacheData = (imageWasTransformed ? nil : downloadedData);
}
[self.imageCache storeImage:transformedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil];
}
[self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
});
} else {
//可以直接看到這一部分
if (downloadedImage && finished) {
if (self.cacheSerializer) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@autoreleasepool {
NSData *cacheData = self.cacheSerializer(downloadedImage, downloadedData, url);
[self.imageCache storeImage:downloadedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil];
}
});
} else {
//對圖片進(jìn)行緩存
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
}
}
//第二次調(diào)用完成的block
[self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
if (finished) {
[self safelyRemoveOperationFromRunning:strongSubOperation];
}
}];
//如果從從緩存中獲取了圖片并且不需要下載
} else if (cachedImage) {
//執(zhí)行完成的回調(diào)
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
[self safelyRemoveOperationFromRunning:strongOperation];
} else {
// 緩存中沒有獲取圖片,也不用下載
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:strongOperation];
}
}];
return operation;
}
總結(jié)一下SDWebImageManager的loadImageWithURL:所做的事情:
其實(shí)在
loadImageWithURL:里面做了加載圖片的完整流程。首先檢查傳入的NSURL的有效性。然后開始從緩存中查找是否有這個(gè)圖片,得到查詢結(jié)果之后再根據(jù)查詢結(jié)果和設(shè)置的option判斷是否需要進(jìn)行下載操作,如果不需要下載操作那么就直接使用cache image進(jìn)行下載回調(diào)。如果需要進(jìn)行下載操作那么就開始下載,下載完成后按照設(shè)置的option將圖片緩存到內(nèi)存和磁盤,最后進(jìn)行完成的回調(diào)。
然后我們看一下查詢緩存的具體過程,也就是SDImageCache這個(gè)類的queryCacheOperationForKey:方法:
這里也是采用注釋的方式
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock {
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// 首先檢查內(nèi)存緩存中有沒有這個(gè)圖片,注意內(nèi)存緩存使用的是NSCache,它是一個(gè)類字典結(jié)構(gòu),使用圖片對應(yīng)的nsurl作為key,在查詢的時(shí)候就用這個(gè)key去查詢
UIImage *image = [self imageFromMemoryCacheForKey:key];
//是否只查詢內(nèi)存緩存(如果從內(nèi)存緩存中獲取了圖片并且沒有設(shè)置SDImageCacheQueryDataWhenInMemory,那么就只查詢內(nèi)存緩存)
BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryDataWhenInMemory));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
//執(zhí)行回調(diào)
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil;
}
NSOperation *operation = [NSOperation new];
void(^queryDiskBlock)(void) = ^{
if (operation.isCancelled) {
// do not call the completion if cancelled
return;
}
@autoreleasepool {
//從磁盤中查詢
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage;
//j緩存獲取的類型,有三種m類型,none,memory,disk
SDImageCacheType cacheType = SDImageCacheTypeNone;
if (image) {
// 圖片是從內(nèi)存緩存中獲取的
diskImage = image;
cacheType = SDImageCacheTypeMemory;
} else if (diskData) {
//圖片是從磁盤緩存中獲取的
cacheType = SDImageCacheTypeDisk;
// 解壓圖片
diskImage = [self diskImageForKey:key data:diskData options:options];
//判斷是否需要把圖片緩存到內(nèi)存
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
//將圖片緩存到內(nèi)存
[self.memCache setObject:diskImage forKey:key cost:cost];
}
}
if (doneBlock) {
if (options & SDImageCacheQueryDiskSync) {
doneBlock(diskImage, diskData, cacheType);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, cacheType);
});
}
}
}
};
if (options & SDImageCacheQueryDiskSync) {
queryDiskBlock();
} else {
dispatch_async(self.ioQueue, queryDiskBlock);
}
return operation;
}
總結(jié)一下queryCacheOperationForKey:方法所做的事情:
SDImageCache這個(gè)類是專門負(fù)責(zé)緩存相關(guān)的問題的,包括查詢緩存和將圖片進(jìn)行緩存。SDImageCache使用了一個(gè)NSCache對象來進(jìn)行內(nèi)存緩存,磁盤緩存則是把圖片數(shù)據(jù)存放在應(yīng)用沙盒的Caches這個(gè)文件夾下。
首先查詢內(nèi)存緩存,內(nèi)存緩存查詢完了以后再判斷是否需要查詢磁盤緩存。如果查詢內(nèi)存緩存已經(jīng)有了結(jié)果并且沒有設(shè)置一定要查詢磁盤緩存,那么就不查詢磁盤緩存,否則就要查詢磁盤緩存。內(nèi)存緩存沒有查詢到圖片,并且磁盤緩存查詢到了圖片,那么就要把這個(gè)內(nèi)容緩存到內(nèi)存緩存中。
圖片的緩存查詢完成后我們再來看一下下載操作,即SDWebImageDownloader的downloadImageWithURL:方法
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
// The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return nil;
}
LOCK(self.operationsLock);
NSOperation<SDWebImageDownloaderOperationInterface> *operation = [self.URLOperations objectForKey:url];
if (!operation || operation.isFinished) {
//創(chuàng)建一下下載的operation
operation = [self createDownloaderOperationWithUrl:url options:options];
__weak typeof(self) wself = self;
operation.completionBlock = ^{
__strong typeof(wself) sself = wself;
if (!sself) {
return;
}
LOCK(sself.operationsLock);
[sself.URLOperations removeObjectForKey:url];
UNLOCK(sself.operationsLock);
};
[self.URLOperations setObject:operation forKey:url];
// Add operation to operation queue only after all configuration done according to Apple's doc.
// `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
//把operation加入到nNSOperationQueue中去
[self.downloadQueue addOperation:operation];
}
UNLOCK(self.operationsLock);
//這一部分代碼是在取消operation的時(shí)候使用
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
token.downloadOperation = operation;
token.url = url;
token.downloadOperationCancelToken = downloadOperationCancelToken;
return token;
}
SDWebImageDownloader這個(gè)類是專門管理下載的,它有一個(gè)屬性是downloadQueue,這是一個(gè)NSOperationQueue,每創(chuàng)建一個(gè)新的下載任務(wù)都把它加入到這個(gè)downloadQueue中,讓downloadQueue去管理任務(wù)的開始,取消,結(jié)束。
上面的方法其實(shí)做的事情很簡單,就是創(chuàng)建了一個(gè)下載圖片的operation,然后把它加入到了
downloadQueue中去。
下面我們來具體看一下創(chuàng)建下載圖片的operation的過程,即SDWebImageDownloader類的createDownloaderOperationWithUrl:方法:
- (NSOperation<SDWebImageDownloaderOperationInterface> *)createDownloaderOperationWithUrl:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options {
NSTimeInterval timeoutInterval = self.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (self.headersFilter) {
request.allHTTPHeaderFields = self.headersFilter(url, [self allHTTPHeaderFields]);
}
else {
request.allHTTPHeaderFields = [self allHTTPHeaderFields];
}
//前面都是為了創(chuàng)建一個(gè)request,然后使用request和session對象去創(chuàng)建下載的operation
NSOperation<SDWebImageDownloaderOperationInterface> *operation = [[self.operationClass alloc] initWithRequest:request inSession:self.session options:options];
operation.shouldDecompressImages = self.shouldDecompressImages;
if (self.urlCredential) {
operation.credential = self.urlCredential;
} else if (self.username && self.password) {
operation.credential = [NSURLCredential credentialWithUser:self.username password:self.password persistence:NSURLCredentialPersistenceForSession];
}
//設(shè)置operation的隊(duì)列優(yōu)先級
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
//如果設(shè)置的執(zhí)行順序是xLIFI,即后進(jìn)先出,則要把queue中的最后一個(gè)加入的operation的依賴設(shè)置為該operation,這樣來保證這個(gè)operation最先執(zhí)行
if (self.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[self.lastAddedOperation addDependency:operation];
self.lastAddedOperation = operation;
}
return operation;
}
這個(gè)方法就是創(chuàng)建了一個(gè)request對象,然后使用這個(gè)request對象和session對象去創(chuàng)建下載的operation對象。
我們看一下負(fù)責(zé)單個(gè)下載任務(wù)的operation對象到底是怎么創(chuàng)建的,即SDWebImageDownloaderOperation類的- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request inSession:(nullable NSURLSession *)session options:(SDWebImageDownloaderOptions)options方法:
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options {
if ((self = [super init])) {
_request = [request copy];
_shouldDecompressImages = YES;
_options = options;
_callbackBlocks = [NSMutableArray new];
_executing = NO;
_finished = NO;
_expectedSize = 0;
_unownedSession = session;
_callbacksLock = dispatch_semaphore_create(1);
_coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL);
}
return self;
}
這個(gè)初始化方法其實(shí)也很簡單,就是給自己的成員變量賦值
我們知道,NSOperation類的真正執(zhí)行任務(wù)是在其start方法里面,那么我們看一下SDWebImageDownloaderOperation的start方法的具體實(shí)現(xiàn):
代碼比較長,我在關(guān)鍵部分加了注釋
- (void)start {
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
#if SD_UIKIT
//這一部分就是解決在后臺仍然進(jìn)行下載的問題
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;
if (sself) {
[sself cancel];
[app endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}
#endif
NSURLSession *session = self.unownedSession;
//創(chuàng)建一個(gè)session對象,因?yàn)楹竺嬉獎?chuàng)建NSURLSessionTask,需要session對象
if (!session) {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15;
/**
* Create the session for this task
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls.
*/
session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
self.ownedSession = session;
}
if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
// Grab the cached data for later check
NSURLCache *URLCache = session.configuration.URLCache;
if (!URLCache) {
URLCache = [NSURLCache sharedURLCache];
}
NSCachedURLResponse *cachedResponse;
// NSURLCache's `cachedResponseForRequest:` is not thread-safe, see https://developer.apple.com/documentation/foundation/nsurlcache#2317483
@synchronized (URLCache) {
cachedResponse = [URLCache cachedResponseForRequest:self.request];
}
if (cachedResponse) {
self.cachedData = cachedResponse.data;
}
}
//創(chuàng)建dataTask,這個(gè)才是真正執(zhí)行下載任務(wù)的
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
}
if (self.dataTask) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
if ([self.dataTask respondsToSelector:@selector(setPriority:)]) {
if (self.options & SDWebImageDownloaderHighPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityHigh;
} else if (self.options & SDWebImageDownloaderLowPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityLow;
}
}
#pragma clang diagnostic pop
[self.dataTask resume];
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
});
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
[self done];
return;
}
#if SD_UIKIT
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return;
}
if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
[app endBackgroundTask:self.backgroundTaskId];
self.backgroundTaskId = UIBackgroundTaskInvalid;
}
#endif
}
這里就是通過一個(gè)session對象和一個(gè)request對象創(chuàng)建了一個(gè)dataTask對象,這個(gè)dataTask對象才是真正用來下載的,然后調(diào)用
[self.dataTask resume]執(zhí)行下載。
到這里SDWebImage的源碼分析就結(jié)束啦。
這篇文章在簡書的地址:SDWebImage源碼解讀

