一、準備知識
在正式學習源碼前,先講一些SDWebImage中用到的生僻知識點,有些用的很頻繁,但是很多人對這些知識點模糊不清,如果不搞清楚會大大影響閱讀效率,比如枚舉NS_OPTIONS的二進制位運算。
1. NS_OPTIONS與位運算
NS_OPTIONS用來定義位移相關操作的枚舉值,當一個枚舉變量需要攜帶多種值的時候就需要,我們可以參考UIKit.Framework的頭文件,可以看到大量的枚舉定義。例如在SDWebImage下面就會接觸到SDWebImageOptions枚舉值:
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
SDWebImageRetryFailed = 1 << 0,
SDWebImageLowPriority = 1 << 1,
SDWebImageCacheMemoryOnly = 1 << 2,
SDWebImageProgressiveDownload = 1 << 3,
SDWebImageRefreshCached = 1 << 4,
SDWebImageContinueInBackground = 1 << 5,
SDWebImageHandleCookies = 1 << 6,
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
SDWebImageHighPriority = 1 << 8,
SDWebImageDelayPlaceholder = 1 << 9,
SDWebImageTransformAnimatedImage = 1 << 10,
SDWebImageAvoidAutoSetImage = 1 << 11,
SDWebImageScaleDownLargeImages = 1 << 12
};
“<<”左移運算符
“<<”是位運算中的左移運算符,第一個值SDWebImageRetryFailed = 1 << 0,十進制1轉化爲二進制:0b00000001,這裏<<0將所有二進制位左移0位,那麼還是0b00000001,最終SDWebImageRetryFailed 值爲1.
第二個枚舉值SDWebImageLowPriority =1<<1,這裏是將1的二進制所有位向左移動1位,空缺的用0補齊,那麼0b00000001變成0b00000010,十進制爲2則SDWebImageLowPriority值爲2。
依次類推:
SDWebImageCacheMemoryOnly向左移動2位等於4,
SDWebImageProgressiveDownload向左移動3位等於8.
下面寫一個🌰,customImageView是我們自定義的imageView實例,在SDWebImage的SDWebImageManager.m具體使用中:
[customImageView sd_setImageWithURL:url placeholderImage:nil options:SDWebImageRetryFailed | SDWebImageCacheMemoryOnly];
‘|’ 或運算
注意到代碼中用到了’|’,’|’’是位運算中的或運算,需要兩個操作數,作用是將兩個數的相同位進行邏輯或運算,即如果兩個對應位有一個位1,則運算後此位爲1,如果兩個對應位都爲0。例如十進制1的二進制0b00000001 | 十進制2的二進制0b00000010,結果爲0b00000011十進制爲3。下圖示例:
當options值爲SDWebImageRetryFailed | SDWebImageCacheMemoryOnly時,執行或運算0b00000001| 0b00000100 = 0b00000101 十進制是5.
‘&’ 與運算
那麼在具體的方法內部options怎麼使用呢?下面的代碼SD將options和SDWebImageRetryFailed做"&"運算:
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
}
‘&’是位運算中的與運算,當對應位數同爲1結果才爲1.例如十進制1的二進制0b00000001&十進制2的二進制0b00000010,結果爲0b00000000十進制是0.
下面代碼中,SD將此時的options二進制0b00000101和SDWebImageRetryFailed的二進制進行&運算,如果options包含了SDWebImageRetryFailed則結果爲真。SD通篇都根據options做了很多事情,因此理解可選枚舉和位運算非常重要。
2.NSURLCredential
當移動端和服務器在傳輸過程中,服務端有可能在返回Response時附帶認證,詢問 HTTP 請求的發起方是誰,這時候發起方應提供正確的用戶名和密碼(即認證信息)。這時候就需要NSURLCredential身份認證,更加具體可以查看這篇博客。
3.涉及的宏定義
3.1 dispatch_main_async_safe(block)
#define dispatch_main_async_safe(block)\
if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
3.2 SD_LOCK與SD_UNLOCK
最新版本的SDK中,開始使用信號量實現鎖,來保證操作的安全
#ifndef SD_LOCK
#define SD_LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
#endif
#ifndef SD_UNLOCK
#define SD_UNLOCK(lock) dispatch_semaphore_signal(lock);
#endif
二、核心源碼解析
SDWebImage的核心Workflow:
2.1 UIImageView+WebCache/UIView+WebCache
UIImageView+WebCache對外使用的API入口,這個類的接口設計把設計模式五大原則之一的接口分離原則體現的淋漓盡致。
首先說一下什麼是接口分離原則:
接口分離原則:爲特定功能提供特定的接口,不要使用單一的總接口包括所有功能,而是應該根據功能把這些接口分割,減少依賴,不能強迫用戶去依賴那些他們不使用的接口。
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
context = [context copy]; // copy to avoid mutable object
//從context獲取對應的validOperationKey,然後根據參數validOperationKey取消當前類所對應的下載Operation對象,如果operationKey爲nil key取NSStringFromClass([self class])
NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
if (!validOperationKey) {
validOperationKey = NSStringFromClass([self class]);
}
self.sd_latestOperationKey = validOperationKey;
//具體的取消操作在UIView+WebCacheOperation中實現
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
self.sd_imageURL = url;
//利用&與運算判斷調用者是否需要設置佔位圖,需要則set佔位圖
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
});
}
if (url) {
// reset the progress
//圖片的大小和已經下載的大小
self.sd_imageProgress.totalUnitCount = 0;
self.sd_imageProgress.completedUnitCount = 0;
#if SD_UIKIT || SD_MAC
// check and start image indicator
//開始圖片加載指示器(菊花動畫),之前的版本這裏有判斷
[self sd_startImageIndicator];
id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif
SDWebImageManager *manager = context[SDWebImageContextCustomManager];
if (!manager) {
manager = [SDWebImageManager sharedManager];
}
//下載進度的Block,獲取下載過程中的一些信息
@weakify(self);
SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
@strongify(self);
NSProgress *imageProgress = self.sd_imageProgress;
imageProgress.totalUnitCount = expectedSize;
imageProgress.completedUnitCount = receivedSize;
#if SD_UIKIT || SD_MAC
if ([imageIndicator respondsToSelector:@selector(updateIndicatorProgress:)]) {
double progress = imageProgress.fractionCompleted;
dispatch_async(dispatch_get_main_queue(), ^{
[imageIndicator updateIndicatorProgress:progress];
});
}
#endif
if (progressBlock) {
progressBlock(receivedSize, expectedSize, targetURL);
}
};
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
@strongify(self);
if (!self) { return; }
// if the progress not been updated, mark it to complete state
if (finished && !error && self.sd_imageProgress.totalUnitCount == 0 && self.sd_imageProgress.completedUnitCount == 0) {
self.sd_imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
self.sd_imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}
#if SD_UIKIT || SD_MAC
// check and stop image indicator
//停止加載動畫,移除菊花
if (finished) {
[self sd_stopImageIndicator];
}
#endif
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
//是否不顯示圖片兩個條件滿足其一即可:
// 1>調用者手動主動配置,且image不爲nil
// 2>沒有圖片並且不delay佔位圖情況
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
if (!self) { return; }
if (!shouldNotSetImage) {
[self sd_setNeedsLayout];
}
if (completedBlock && shouldCallCompletedBlock) {
completedBlock(image, data, error, cacheType, finished, url);
}
};
// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
// OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
//如果設置了不自動顯示圖片,則回調讓調用者手動添加顯示圖片 程序return
if (shouldNotSetImage) {
dispatch_main_async_safe(callCompletedBlockClojure);
return;
}
UIImage *targetImage = nil;
NSData *targetData = nil;
if (image) {
// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
targetImage = placeholder;
targetData = nil;
}
#if SD_UIKIT || SD_MAC
// check whether we should use the image transition
SDWebImageTransition *transition = nil;
if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
transition = self.sd_imageTransition;
}
#endif
//直接在主線程給View或者UIButton設置image
dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC
[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else
[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endif
callCompletedBlockClojure();
});
}];
//綁定operation到當前self,key=validOperationKey,value=operation
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
#if SD_UIKIT || SD_MAC
//移除菊花
[self sd_stopImageIndicator];
#endif
//在主線程拋出url爲nil的回調
dispatch_main_async_safe(^{
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
}
});
}
}
下面是在這一過程的簡易流程圖幫助理解:
2.2 SDWebImageManager
SDWebImageManager類是SDWebImage中的核心類,主要負責調用SDWebImageDownloader進行圖片下載,以及在下載之後利用SDImageCache進行圖片緩存。並且此類還可以跳過UIImageViewe/Cache或者UIView/Cache單獨使用,不僅侷限於一個UIView。
SDWebImageManager.h註解:
//生成一個SDWebImagemanager的單例
+ (nonnull instancetype)sharedManager {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
//根據URL獲取緩存中的key
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url {
return [self cacheKeyForURL:url cacheKeyFilter:self.cacheKeyFilter];
}
//在指定的cacheKeyFilter返回URL對應的key
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url cacheKeyFilter:(id<SDWebImageCacheKeyFilter>)cacheKeyFilter {
if (!url) {
return @"";
}
if (cacheKeyFilter) {
return [cacheKeyFilter cacheKeyForURL:url];
} else {
return url.absoluteString;
}
}
- (SDWebImageCombinedOperation *)loadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDInternalCompletionBlock)completedBlock {
return [self loadImageWithURL:url options:options context:nil progress:progressBlock completed:completedBlock];
}
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock {
// Invoking this method without a completedBlock is pointless
//completedBlock爲nil,則觸發斷沿,程序crash
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
// Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
// throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
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;
BOOL isFailedUrl = NO;
if (url) {
//爲了防止在多線程訪問出現問題,創建鎖。檢查URL是否在failedURLs數組中
//SD_LOCK 是一個宏,GCD信號量做鎖
SD_LOCK(self.failedURLsLock);
isFailedUrl = [self.failedURLs containsObject:url];
SD_UNLOCK(self.failedURLsLock);
}
//如果url爲nil,或者沒有設置“SDWebImageRetryFailed”且該url已經下載失敗過,那麼返回失敗的回調
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}] url:url];
return operation;
}
//創建SD_LOCk,添加operation到數組中
SD_LOCK(self.runningOperationsLock);
[self.runningOperations addObject:operation];
SD_UNLOCK(self.runningOperationsLock);
// Preprocess the context arg to provide the default value from manager
//預處理上下文參數,從manager中提供默認值
context = [self processedContextWithContext:context];
// Start the entry to load image from cache
[self callCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];
return operation;
}
//取消或有的下載操作
- (void)cancelAll {
SD_LOCK(self.runningOperationsLock);
NSSet<SDWebImageCombinedOperation *> *copiedOperations = [self.runningOperations copy];
SD_UNLOCK(self.runningOperationsLock);
[copiedOperations makeObjectsPerformSelector:@selector(cancel)]; // This will call `safelyRemoveOperationFromRunning:` and remove from the array
}
//判斷是否有下載圖片
- (BOOL)isRunning {
BOOL isRunning = NO;
SD_LOCK(self.runningOperationsLock);
isRunning = (self.runningOperations.count > 0);
SD_UNLOCK(self.runningOperationsLock);
return isRunning;
}
#pragma mark - Private
// Query cache process
- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// Check whether we should query cache
//檢查是否需要查詢緩存
//默認是檢查緩存,設置“SDWebImageFromLoaderOnly”後,直接下載
BOOL shouldQueryCache = (options & SDWebImageFromLoaderOnly) == 0;
if (shouldQueryCache) {
id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
@weakify(operation);
//開啓一個線程,執行通過key從緩存和磁盤中獲取圖片的任務
operation.cacheOperation = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
@strongify(operation);
//如果線程已經取消或operation==nil,說明已經從緩存和磁盤中獲取到圖片,return
if (!operation || operation.isCancelled) {
[self safelyRemoveOperationFromRunning:operation];
return;
}
// Continue download process
//如果沒有從緩存和磁盤中獲取到圖片,則直接網絡中下載
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
}];
} else {
// Continue download process
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
}
}
// Download process
- (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(SDWebImageContext *)context
cachedImage:(nullable UIImage *)cachedImage
cachedData:(nullable NSData *)cachedData
cacheType:(SDImageCacheType)cacheType
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// Check whether we should download image from network
//先檢查是否需要下載圖片
BOOL shouldDownload = (options & SDWebImageFromCacheOnly) == 0;
shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
shouldDownload &= [self.imageLoader canRequestImageForURL:url];
if (shouldDownload) {
//1、緩存有圖片
//2、設置了"SDWebImageRefreshCached",需要更新緩存
//重新下載圖片,更新緩存
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:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
// Pass the cached image to the image loader. The image loader should check whether the remote image is equal to the cached image.
SDWebImageMutableContext *mutableContext;
if (context) {
mutableContext = [context mutableCopy];
} else {
mutableContext = [NSMutableDictionary dictionary];
}
mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
context = [mutableContext copy];
}
// `SDWebImageCombinedOperation` -> `SDWebImageDownloadToken` -> `downloadOperationCancelToken`, which is a `SDCallbacksDictionary` and retain the completed block below, so we need weak-strong again to avoid retain cycle
@weakify(operation);
operation.loaderOperation = [self.imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
@strongify(operation);
if (!operation || operation.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 (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {
// Image refresh hit the NSURLCache cache, do not call the completion block
} else if (error) {
//下載是錯誤,判斷是否限制URL
[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error];
if (shouldBlockFailedURL) {
SD_LOCK(self.failedURLsLock);
[self.failedURLs addObject:url];
SD_UNLOCK(self.failedURLsLock);
}
} else {
//如果設置了"SDWebImageRetryFailed",從failedURLs中移除url
if ((options & SDWebImageRetryFailed)) {
SD_LOCK(self.failedURLsLock);
[self.failedURLs removeObject:url];
SD_UNLOCK(self.failedURLsLock);
}
// Store cache process
//存儲緩存的進程
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
}
//移除operation
if (finished) {
[self safelyRemoveOperationFromRunning:operation];
}
}];
} else if (cachedImage) {
//這裏其實就是返回completedBlock
[self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
} else {
// Image not in cache and download disallowed by delegate
//這裏也是返回completedBlock
[self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}
// Store cache process
- (void)callStoreCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(SDWebImageContext *)context
downloadedImage:(nullable UIImage *)downloadedImage
downloadedData:(nullable NSData *)downloadedData
finished:(BOOL)finished
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
//獲取圖片的存儲類型
//SDImageCacheTypeNone
//SDImageCacheTypeDisk 存儲在磁盤
//SDImageCacheTypeMemory 存儲在緩存
//SDImageCacheTypeAll 同時存儲在磁盤和緩存
SDImageCacheType storeCacheType = SDImageCacheTypeAll;
if (context[SDWebImageContextStoreCacheType]) {
storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
}
id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
//Defaults to nil, which means no transform is applied.
//默認情況下,不轉換成另一張圖片
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
//圖片已經下載
//不是動圖 || options 設置"SDWebImageTransformAnimatedImage"
//transformer == YES
if (downloadedImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage)) && transformer) {
//獲取 全局線程 異步執行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@autoreleasepool {
//Transform the image to another image.轉換成另一張圖片
UIImage *transformedImage = [transformer transformedImageWithImage:downloadedImage forKey:key];
if (transformedImage && finished) {
//return The cache key to appended after the original cache key.
NSString *transformerKey = [transformer transformerKey];
NSString *cacheKey = SDTransformedKeyForKey(key, transformerKey);
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
NSData *cacheData;
// pass nil if the image was transformed, so we can recalculate the data from the image
//如果image 被轉換,cacheData傳遞nil,imageCache會重新計算data
if (cacheSerializer && (storeCacheType == SDImageCacheTypeDisk || storeCacheType == SDImageCacheTypeAll)) {
//高速緩存序列化器用於將解碼後的圖像(源下載數據)轉換爲用於存儲到磁盤高速緩存的實際數據。 如果返回nil,則表示從圖像實例生成數據,
cacheData = [cacheSerializer cacheDataWithImage:transformedImage originalData:(imageWasTransformed ? nil : downloadedData) imageURL:url];
} else {
cacheData = (imageWasTransformed ? nil : downloadedData);
}
//存儲image到disk/memory
[self.imageCache storeImage:transformedImage imageData:cacheData forKey:cacheKey cacheType:storeCacheType completion:nil];
}
[selfcallCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
});
} else {//不轉換,直接存儲
if (downloadedImage && finished) {
if (cacheSerializer && (storeCacheType == SDImageCacheTypeDisk || storeCacheType == SDImageCacheTypeAll)) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@autoreleasepool {
//高速緩存序列化器用於將解碼後的圖像(源下載數據)轉換爲用於存儲到磁盤高速緩存的實際數據。 如果返回nil,則表示從圖像實例生成數據,
NSData *cacheData = [cacheSerializer cacheDataWithImage:downloadedImage originalData:downloadedData imageURL:url];
[self.imageCache storeImage:downloadedImage imageData:cacheData forKey:key cacheType:storeCacheType completion:nil];
}
});
} else {
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key cacheType:storeCacheType completion:nil];
}
}
[self callCompletionBlockForOperation:operation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
#pragma mark - Helper
//安全的移除operation
- (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation {
if (!operation) {
return;
}
SD_LOCK(self.runningOperationsLock);
[self.runningOperations removeObject:operation];
SD_UNLOCK(self.runningOperationsLock);
}
- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
completion:(nullable SDInternalCompletionBlock)completionBlock
error:(nullable NSError *)error
url:(nullable NSURL *)url {
[self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url];
}
//只是返回completionBlock
- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
completion:(nullable SDInternalCompletionBlock)completionBlock
image:(nullable UIImage *)image
data:(nullable NSData *)data
error:(nullable NSError *)error
cacheType:(SDImageCacheType)cacheType
finished:(BOOL)finished
url:(nullable NSURL *)url {
dispatch_main_async_safe(^{
if (operation && !operation.isCancelled && completionBlock) {
completionBlock(image, data, error, cacheType, finished, url);
}
});
}
- (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url
error:(nonnull NSError *)error {
// Check whether we should block failed url
BOOL shouldBlockFailedURL;
if ([self.delegate respondsToSelector:@selector(imageManager:shouldBlockFailedURL:withError:)]) {
shouldBlockFailedURL = [self.delegate imageManager:self shouldBlockFailedURL:url withError:error];
} else {
shouldBlockFailedURL = [self.imageLoader shouldBlockFailedURLWithURL:url error:error];
}
return shouldBlockFailedURL;
}
//設置默認的上下文環境:context
- (SDWebImageContext *)processedContextWithContext:(SDWebImageContext *)context {
SDWebImageMutableContext *mutableContext = [SDWebImageMutableContext dictionary];
// Image Transformer from manager
if (!context[SDWebImageContextImageTransformer]) {
// Defaults to nil, which means no transform is applied.
id<SDImageTransformer> transformer = self.transformer;
[mutableContext setValue:transformer forKey:SDWebImageContextImageTransformer];
}
// Cache key filter from manager
if (!context[SDWebImageContextCacheKeyFilter]) {
id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter;
[mutableContext setValue:cacheKeyFilter forKey:SDWebImageContextCacheKeyFilter];
}
// Cache serializer from manager
if (!context[SDWebImageContextCacheSerializer]) {
id<SDWebImageCacheSerializer> cacheSerializer = self.cacheSerializer;
[mutableContext setValue:cacheSerializer forKey:SDWebImageContextCacheSerializer];
}
if (mutableContext.count == 0) {
return context;
} else {
[mutableContext addEntriesFromDictionary:context];
return [mutableContext copy];
}
}
SDWebImageManager的關鍵節點流程圖:
2.3 SDImageCache
SDImageCache是SDWebImage的緩存中心。分三部分組成memory內存緩存,disk硬盤緩存和無緩存組成。
圖片緩存類型
typedef NS_ENUM(NSInteger, SDImageCacheType) {
SDImageCacheTypeNone,
//緩存到磁盤
SDImageCacheTypeDisk,
//緩存到內存
SDImageCacheTypeMemory,
//磁盤和內存都緩存
SDImageCacheTypeAll
};
//內存緩存對象
@property (nonatomic, strong, nonnull) id<SDMemoryCache> memCache;
//磁盤緩存對象
@property (nonatomic, strong, nonnull) id<SDDiskCache> diskCache;
//緩存配置對象,包含所有配置項
@property (nonatomic, copy, readwrite, nonnull) SDImageCacheConfig *config;
//磁盤緩存路徑
@property (nonatomic, copy, readwrite, nonnull) NSString *diskCachePath;
//執行處理輸入輸出的隊列
@property (nonatomic, strong, nullable) dispatch_queue_t ioQueue;
//主要方法註解
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toMemory:(BOOL)toMemory
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
// if memory cache is enabled
//緩存到內存
if (toMemory && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = image.sd_memoryCost;
[self.memCache setObject:image forKey:key cost:cost];
}
if (toDisk) {
//異步操作,緩存到磁盤
dispatch_async(self.ioQueue, ^{
@autoreleasepool {
NSData *data = imageData;
if (!data && image) {
// If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
//如果我們沒有任何數據來檢測圖像格式,請檢查它是否包含使用PNG或JPEG格式的Alpha通道
//在前面,當image被轉換時,傳入的data爲nil,在這裏重新計算
SDImageFormat format;
//選擇圖片的格式
if ([SDImageCoderHelper CGImageContainsAlpha:image.CGImage]) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
}
data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil];
}
//將圖片存儲到磁盤
[self _storeImageDataToDisk:data forKey:key];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
} else {
if (completionBlock) {
completionBlock();
}
}
}
//存儲image到緩存
- (void)storeImageToMemory:(UIImage *)image forKey:(NSString *)key {
if (!image || !key) {
return;
}
NSUInteger cost = image.sd_memoryCost;
[self.memCache setObject:image forKey:key cost:cost];
}
//存儲Image到磁盤
- (void)storeImageDataToDisk:(nullable NSData *)imageData
forKey:(nullable NSString *)key {
if (!imageData || !key) {
return;
}
dispatch_sync(self.ioQueue, ^{
[self _storeImageDataToDisk:imageData forKey:key];
});
}
// Make sure to call form io queue by caller
- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
if (!imageData || !key) {
return;
}
[self.diskCache setData:imageData forKey:key];
}
#pragma mark - Query and Retrieve Ops
//異步檢查圖片是否緩存在磁盤中
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDImageCacheCheckCompletionBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
BOOL exists = [self _diskImageDataExistsWithKey:key];
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(exists);
});
}
});
}
- (BOOL)diskImageDataExistsWithKey:(nullable NSString *)key {
if (!key) {
return NO;
}
__block BOOL exists = NO;
dispatch_sync(self.ioQueue, ^{
exists = [self _diskImageDataExistsWithKey:key];
});
return exists;
}
// Make sure to call form io queue by caller
- (BOOL)_diskImageDataExistsWithKey:(nullable NSString *)key {
if (!key) {
return NO;
}
return [self.diskCache containsDataForKey:key];
}
//根據key返回磁盤中imageData
- (nullable NSData *)diskImageDataForKey:(nullable NSString *)key {
if (!key) {
return nil;
}
__block NSData *imageData = nil;
dispatch_sync(self.ioQueue, ^{
imageData = [self diskImageDataBySearchingAllPathsForKey:key];
});
return imageData;
}
//根據key返回緩存中image
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
return [self.memCache objectForKey:key];
}
//根據key返回磁盤中image
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
[self.memCache setObject:diskImage forKey:key cost:cost];
}
return diskImage;
}
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key {
// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
return image;
}
// Second check the disk cache...
image = [self imageFromDiskCacheForKey:key];
return image;
}
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
if (!key) {
return nil;
}
NSData *data = [self.diskCache dataForKey:key];
if (data) {
return data;
}
// Addtional cache path for custom pre-load cache
if (self.additionalCachePathBlock) {
NSString *filePath = self.additionalCachePathBlock(key);
if (filePath) {
data = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];
}
}
return data;
}
//使用操作異步查詢緩存,並在完成後調用完成。
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
if (transformer) {
// grab the transformed disk image if transformer provided
NSString *transformerKey = [transformer transformerKey];
key = SDTransformedKeyForKey(key, transformerKey);
}
// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
if ((options & SDImageCacheDecodeFirstFrameOnly) && image.sd_isAnimated) {
#if SD_MAC
image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];
#else
image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
#endif
}
BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryMemoryData));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil;
}
// Second check the disk cache...
NSOperation *operation = [NSOperation new];
// Check whether we need to synchronously query disk
// 1. in-memory cache hit & memoryDataSync
// 2. in-memory cache miss & diskDataSync
BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
(!image && options & SDImageCacheQueryDiskDataSync));
void(^queryDiskBlock)(void) = ^{
if (operation.isCancelled) {
// do not call the completion if cancelled
return;
}
@autoreleasepool {
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage;
SDImageCacheType cacheType = SDImageCacheTypeNone;
if (image) {
// the image is from in-memory cache, but need image data
diskImage = image;
cacheType = SDImageCacheTypeMemory;
} else if (diskData) {
cacheType = SDImageCacheTypeDisk;
// decode image data only if in-memory cache missed
diskImage = [self diskImageForKey:key data:diskData options:options context:context];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
[self.memCache setObject:diskImage forKey:key cost:cost];
}
}
if (doneBlock) {
if (shouldQueryDiskSync) {
doneBlock(diskImage, diskData, cacheType);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, cacheType);
});
}
}
}
};
// Query in ioQueue to keep IO-safe
if (shouldQueryDiskSync) {
dispatch_sync(self.ioQueue, queryDiskBlock);
} else {
dispatch_async(self.ioQueue, queryDiskBlock);
}
return operation;
}
#pragma mark - Cache Info
//Returns the total size (in bytes) of data in this cache.
//返回磁盤緩存數據的大小(bytes),可能會阻塞線程
- (NSUInteger)totalDiskSize {
__block NSUInteger size = 0;
dispatch_sync(self.ioQueue, ^{
size = [self.diskCache totalSize];
});
return size;
}
//Returns the number of data in this cache.
//返回磁盤緩存數據的數量,可能會阻塞線程
- (NSUInteger)totalDiskCount {
__block NSUInteger count = 0;
dispatch_sync(self.ioQueue, ^{
count = [self.diskCache totalCount];
});
return count;
}
//異步計算硬盤的size
- (void)calculateSizeWithCompletionBlock:(nullable SDImageCacheCalculateSizeBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
NSUInteger fileCount = [self.diskCache totalCount];
NSUInteger totalSize = [self.diskCache totalSize];
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(fileCount, totalSize);
});
}
});
}
@end
@implementation SDImageCache (SDImageCache)
#pragma mark - SDImageCache
//根據指定的key在圖片緩存中尋找緩存的圖片,The operation可以取消查詢
- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
SDImageCacheOptions cacheOptions = 0;
if (options & SDWebImageQueryMemoryData) cacheOptions |= SDImageCacheQueryMemoryData;
if (options & SDWebImageQueryMemoryDataSync) cacheOptions |= SDImageCacheQueryMemoryDataSync;
if (options & SDWebImageQueryDiskDataSync) cacheOptions |= SDImageCacheQueryDiskDataSync;
if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages;
if (options & SDWebImageAvoidDecodeImage) cacheOptions |= SDImageCacheAvoidDecodeImage;
if (options & SDWebImageDecodeFirstFrameOnly) cacheOptions |= SDImageCacheDecodeFirstFrameOnly;
if (options & SDWebImagePreloadAllFrames) cacheOptions |= SDImageCachePreloadAllFrames;
return [self queryCacheOperationForKey:key options:cacheOptions context:context done:completionBlock];
}
//把圖片存儲到指定位置
- (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock {
switch (cacheType) {
case SDImageCacheTypeNone: {
[self storeImage:image imageData:imageData forKey:key toMemory:NO toDisk:NO completion:completionBlock];
}
break;
case SDImageCacheTypeMemory: {
[self storeImage:image imageData:imageData forKey:key toMemory:YES toDisk:NO completion:completionBlock];
}
break;
case SDImageCacheTypeDisk: {
[self storeImage:image imageData:imageData forKey:key toMemory:NO toDisk:YES completion:completionBlock];
}
break;
case SDImageCacheTypeAll: {
[self storeImage:image imageData:imageData forKey:key toMemory:YES toDisk:YES completion:completionBlock];
}
break;
default: {
if (completionBlock) {
completionBlock();
}
}
break;
}
}
//把指定key路徑的圖片,移動到cacheType位置;如果cacheType是內存,則同步調用completion,否則異步調用
- (void)removeImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock {
switch (cacheType) {
case SDImageCacheTypeNone: {
[self removeImageForKey:key fromMemory:NO fromDisk:NO withCompletion:completionBlock];
}
break;
case SDImageCacheTypeMemory: {
[self removeImageForKey:key fromMemory:YES fromDisk:NO withCompletion:completionBlock];
}
break;
case SDImageCacheTypeDisk: {
[self removeImageForKey:key fromMemory:NO fromDisk:YES withCompletion:completionBlock];
}
break;
case SDImageCacheTypeAll: {
[self removeImageForKey:key fromMemory:YES fromDisk:YES withCompletion:completionBlock];
}
break;
default: {
if (completionBlock) {
completionBlock();
}
}
break;
}
}
//檢查圖像緩存中是否包含給定key的圖像(不加載圖像)。如果映像緩存在內存中,則同步調用completion,否則異步調用completion。
- (void)containsImageForKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheContainsCompletionBlock)completionBlock {
switch (cacheType) {
case SDImageCacheTypeNone: {
if (completionBlock) {
completionBlock(SDImageCacheTypeNone);
}
}
break;
case SDImageCacheTypeMemory: {
BOOL isInMemoryCache = ([self imageFromMemoryCacheForKey:key] != nil);
if (completionBlock) {
completionBlock(isInMemoryCache ? SDImageCacheTypeMemory : SDImageCacheTypeNone);
}
}
break;
case SDImageCacheTypeDisk: {
[self diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
if (completionBlock) {
completionBlock(isInDiskCache ? SDImageCacheTypeDisk : SDImageCacheTypeNone);
}
}];
}
break;
case SDImageCacheTypeAll: {
BOOL isInMemoryCache = ([self imageFromMemoryCacheForKey:key] != nil);
if (isInMemoryCache) {
if (completionBlock) {
completionBlock(SDImageCacheTypeMemory);
}
return;
}
[self diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
if (completionBlock) {
completionBlock(isInDiskCache ? SDImageCacheTypeDisk : SDImageCacheTypeNone);
}
}];
}
break;
default:
if (completionBlock) {
completionBlock(SDImageCacheTypeNone);
}
break;
}
}
//清除不同地方的緩存內容
- (void)clearWithCacheType:(SDImageCacheType)cacheType completion:(SDWebImageNoParamsBlock)completionBlock {
switch (cacheType) {
case SDImageCacheTypeNone: {
if (completionBlock) {
completionBlock();
}
}
break;
case SDImageCacheTypeMemory: {
[self clearMemory];
if (completionBlock) {
completionBlock();
}
}
break;
case SDImageCacheTypeDisk: {
[self clearDiskOnCompletion:completionBlock];
}
break;
case SDImageCacheTypeAll: {
[self clearMemory];
[self clearDiskOnCompletion:completionBlock];
}
break;
default: {
if (completionBlock) {
completionBlock();
}
}
break;
}
}
此篇文章講解的是UIImageView+WebCache/UIView+WebCache
,SDWebImageManager
和SDImageCache
三個關鍵類,有許多很值得我們去學習的點,例如:
1.善用接口分離原則-設計更好的對外調用API。
2.適當做異常處理機制-這些異常處理可以避免消耗不必要的資源或者異常發生。例如SDWebImageManager中
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;
}
3.增加互斥鎖-起到線程的保護作用。
//老版本
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
//新版本
SD_LOCK(self.failedURLsLock);
isFailedUrl = [self.failedURLs containsObject:url];
SD_UNLOCK(self.failedURLsLock);
4.多利用inline函數-提高程序的執行效率。
FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
#if SD_MAC
return image.size.height * image.size.width;
#elif SD_UIKIT || SD_WATCH
return image.size.height * image.size.width * image.scale * image.scale;
#endif
}
5.巧妙利用封裝思想和分層概念,寫出更加平臺化的組件
例如我們日常使用SD大多都是利用UIImageView+WebCache的API,其實SDWebImageManager是完全可以抽出來單獨使用,不會因爲跳過了UIImageView+WebCache沒了依賴而無法使用。
參考文獻: