看了下距離上次寫簡書博客的時間,已經(jīng)過去了八個多月了,很慚愧。正好最近項目不忙,抽點時間研究下第三方庫,朋友建議總結(jié)寫成博客就這樣開篇了。內(nèi)容篇幅會比較長,所以希望各位看官搬好小板凳看SDWebImage源碼解析,如果沒有毅力真的是很難堅持下去。希望大家可以堅持跟著博主一塊學(xué)完SDWebImage源碼系列。

一.準(zhǔn)備知識
在正式學(xué)習(xí)源碼前,先講一些SDWebImage中用到的生僻知識點,有些用的很頻繁,但是很多人對這些知識點模糊不清,如果不搞清楚會大大影響閱讀效率,比如枚舉NS_OPTIONS的二進(jìn)制位運算。
1> NS_OPTIONS與位運算
NS_OPTIONS用來定義位移相關(guān)操作的枚舉值,當(dāng)一個枚舉變量需要攜帶多種值的時候就需要,我們可以參考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,十進(jìn)制1轉(zhuǎn)化為二進(jìn)制:0b00000001,這里<<0將所有二進(jìn)制位左移0位,那么還是0b00000001,最終SDWebImageRetryFailed 值為1.
第二個枚舉值SDWebImageLowPriority =1<<1,這里是將1的二進(jìn)制所有位向左移動1位,空缺的用0補(bǔ)齊,那么0b00000001變成0b00000010,十進(jìn)制為2則SDWebImageLowPriority值為2。

依次類推:
SDWebImageCacheMemoryOnly向左移動2位等于4,
SDWebImageProgressiveDownload向左移動3位等于8.
下面寫一個??,customImageView是我們自定義的imageView實例,在SDWebImage的SDWebImageManager.m具體使用中:
[customImageView sd_setImageWithURL:url placeholderImage:nil options:SDWebImageRetryFailed | SDWebImageCacheMemoryOnly];
注意到代碼中用到了"|",‘|’是位運算中的或運算,需要兩個操作數(shù),作用是將兩個數(shù)的相同位進(jìn)行邏輯或運算,即如果兩個對應(yīng)位有一個位1,則運算后此位為1,如果兩個對應(yīng)位都為0。例如十進(jìn)制1的二進(jìn)制0b00000001 | 十進(jìn)制2的二進(jìn)制0b00000010,結(jié)果為0b00000011十進(jìn)制為3。下圖示例:

當(dāng)options值為SDWebImageRetryFailed | SDWebImageCacheMemoryOnly時,執(zhí)行或運算0b00000001| 0b00000100 = 0b00000101 十進(jìn)制是5.
那么在具體的方法內(nèi)部options怎么使用呢?下面的代碼SD將options和SDWebImageRetryFailed做"&"運算:
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
}
‘&’是位運算中的與運算,當(dāng)對應(yīng)位數(shù)同為1結(jié)果才為1.例如十進(jìn)制1的二進(jìn)制0b00000001&十進(jìn)制2的二進(jìn)制0b00000010,結(jié)果為0b00000000十進(jìn)制是0.

下面代碼中,SD將此時的options二進(jìn)制0b00000101和SDWebImageRetryFailed的二進(jìn)制進(jìn)行&運算,如果options包含了SDWebImageRetryFailed則結(jié)果為真。SD通篇都根據(jù)options做了很多事情,因此理解可選枚舉和位運算非常重要。
2> NSURLCredential
當(dāng)移動端和服務(wù)器在傳輸過程中,服務(wù)端有可能在返回Response時附帶認(rèn)證,詢問 HTTP 請求的發(fā)起方是誰,這時候發(fā)起方應(yīng)提供正確的用戶名和密碼(即認(rèn)證信息)。這時候就需要NSURLCredential身份認(rèn)證,更加具體可以查看這篇博客。
3>涉及的宏定義
3-1>FOUNDATION_EXPORT:
用來定義常量,和#define作用一樣,只不過在檢測字符串的值是否相等是比#define的效率更高,因為比較的是指針地址。
3-2>NS_DESIGNATED_INITIALIZER :
NS_DESIGNATED_INITIALIZER宏來實現(xiàn)指定構(gòu)造器,通常是想告訴調(diào)用者要用這個方法去初始化類對象,便于規(guī)范API。
3-2>__deprecated_msg
用于提示此方法或?qū)傩砸呀?jīng)廢棄。
@property (nonatomic, assign) BOOL shouldUseCredentialStorage __deprecated_msg("Property deprecated. Does nothing. Kept only for backwards compatibility");
4>initialize
initialize靜態(tài)方法會在第一次使用該類之前由運行期系統(tǒng)調(diào)用,而且僅調(diào)用一次,屬于懶加載范疇,如果不使用則不會調(diào)用,可以在方法內(nèi)部做一些初始化操作,但是load方法是只要啟動程序就會調(diào)用。關(guān)于initialize和load更加詳細(xì)的看這里。
5>dispatch_barrier_sync
GCD中的知識點,承上啟下,當(dāng)把任務(wù)A添加到隊列中使用dispatch_barrier_sync時,它會等待在它前面插入隊列的任務(wù)先執(zhí)行完,然后等任務(wù)執(zhí)行完再執(zhí)行后面的任務(wù)。更加詳細(xì)的可點這里。
目前整理了這些,如果還有其他必要講的話,下文會再做解釋。大家有不熟悉的知識點也可以在評論中回復(fù),我會挑痛點比較多的及時更新到博客中。
二.核心源碼解析
SDWebImage的核心Workflow:

我們要研究的源碼主要是圍繞這些核心類展開。
閱讀建議TIPS:SDWebImage的源碼多,邏輯復(fù)雜,我的建議是讀者下載一份源碼,源碼和本文同步閱讀,因為如果沒看過源碼的調(diào)用邏輯,單看本文的解讀不會形成體系。會在源碼上加注釋,并且copy到文章中,運行項目參考調(diào)用棧配合本文效果會更好。
1>UIImageView+WebCache/UIView+WebCache
UIImageView+WebCache對外使用的API入口,這個類的接口設(shè)計把設(shè)計模式五大原則之一的接口分離原則體現(xiàn)的淋漓盡致。首先說一下什么是接口分離原則:
接口分離原則:為特定功能提供特定的接口,不要使用單一的總接口包括所有功能,而是應(yīng)該根據(jù)功能把這些接口分割,減少依賴,不能強(qiáng)迫用戶去依賴那些他們不使用的接口。
在.h中可以看到:
- (void)sd_setImageWithURL:(nullable NSURL *)url NS_REFINED_FOR_SWIFT;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder;
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
這樣調(diào)用者需要什么功能就去調(diào)用特定的API,清晰易擴(kuò)展,在.m中會設(shè)計一個總的接口包含所有功能。UIImageView+WebCache主要是一個接口,沒有太多需要琢磨的,在.m中總接口中又調(diào)用了UIView的擴(kuò)展方法,接下來講一下UIView+WebCache。
UIView+WebCache提供了具體的圖片加載請求,UIButton和UIImageView都可調(diào)用sd_internalSetImageWithURL來實現(xiàn),下面具體看下sd_internalSetImageWithURL的實現(xiàn)。具體的源碼意思都做了注釋:
- (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
context:(nullable NSDictionary *)context {
//根據(jù)參數(shù)operationKey取消當(dāng)前類所對應(yīng)的下載Operation對象,如果operationKey為nil key取NSStringFromClass([self class])
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
//具體的取消操作在UIView+WebCacheOperation中實現(xiàn)
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
//利用關(guān)聯(lián)對象給當(dāng)前self實例綁定url key=imageURLKey value=url
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//這里就用到了我們開篇講的位運算,利用&與運算判斷調(diào)用者是否需要設(shè)置占位圖,需要則set
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
if (url) {
// check if activityView is enabled or not
// 判斷之前是否利用關(guān)聯(lián)對象給self設(shè)置了顯示菊花加載,如果有則add
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
__weak __typeof(self)wself = self;
//調(diào)用SDWebImageManager的loadImageWithURL方法去加載圖片,返回值是SDWebImageCombinedOperation
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
//在這里移除菊花
[sself sd_removeActivityIndicator];
if (!sself) { return; }
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
//是否不顯示圖片兩個條件滿足其一即可 1>調(diào)用者手動主動配置,哪怕image不為nil 2>沒有圖片并且不delaye占位圖情況
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
if (!sself) { return; }
if (!shouldNotSetImage) {
[sself sd_setNeedsLayout];
}
//如果設(shè)置了不自動顯示圖片,則回調(diào)讓調(diào)用者手動添加顯示圖片 程序return
if (completedBlock && shouldCallCompletedBlock) {
completedBlock(image, error, cacheType, 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
if (shouldNotSetImage) {//如果設(shè)置了不自動顯示圖片,則回調(diào)讓調(diào)用者手動添加顯示圖片 程序return
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) {//如果沒有image,并且調(diào)用者設(shè)置了delaye顯示默認(rèn)圖那這里targetImage設(shè)置為placeholder
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
targetImage = placeholder;
targetData = nil;
}
BOOL shouldUseGlobalQueue = NO;
//外部參數(shù)context如果設(shè)置了全局隊列中setImage,那shouldUseGlobalQueue為YES,否則默認(rèn)在dispatch_get_main_queue
if (context && [context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey]) {
shouldUseGlobalQueue = [[context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey] boolValue];
}
dispatch_queue_t targetQueue = shouldUseGlobalQueue ? dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) : dispatch_get_main_queue();
dispatch_queue_async_safe(targetQueue, ^{//隊列中設(shè)置image給imageView或者button
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
dispatch_main_async_safe(callCompletedBlockClojure);
});
}];
//綁定operation到當(dāng)前self,key=validOperationKey,value=operation
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {v
dispatch_main_async_safe(^{
//移除菊花 拋出url為nil的回調(diào)
[self sd_removeActivityIndicator];
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
下面是在這一過程的簡易流程圖幫助理解:
2>SDWebImageManager
SDWebImageManager類是SDWebImage中的核心類,主要負(fù)責(zé)調(diào)用SDWebImageDownloader進(jìn)行圖片下載,以及在下載之后利用SDImageCache進(jìn)行圖片緩存。并且此類還可以跳過UIImageViewe/Cache或者UIView/Cache單獨使用,不僅局限于一個UIView。
SDWebImageManager.h注解:
@class SDWebImageManager;
@protocol SDWebImageManagerDelegate <NSObject>
@optional
//當(dāng)緩存沒有發(fā)現(xiàn)當(dāng)前圖片,那么會查看調(diào)用者是否實現(xiàn)改方法,如果return一個no,則不會繼續(xù)下載這張圖片
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL;
//當(dāng)圖片下載完成但是未添加到緩存里面,這時候調(diào)用該方法可以給圖片旋轉(zhuǎn)方向,注意是異步執(zhí)行, 防止組織主線程
- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;
@end
@interface SDWebImageManager : NSObject
//SDWebImageManagerDelegate的delegate
@property (weak, nonatomic, nullable) id <SDWebImageManagerDelegate> delegate;
//緩存中心
@property (strong, nonatomic, readonly, nullable) SDImageCache *imageCache;
//下載中心
@property (strong, nonatomic, readonly, nullable) SDWebImageDownloader *imageDownloader;
//這個緩存block的作用是,在block內(nèi)部進(jìn)行緩存key的生成并return,key就是根據(jù)圖片url根據(jù)規(guī)則生成,sd的緩存策略就是key是圖片url,value就是image
@property (nonatomic, copy, nullable) SDWebImageCacheKeyFilterBlock cacheKeyFilter;
//返回SDWebImageManager的單例
+ (nonnull instancetype)sharedManager;
//根據(jù)特定的cache和downloader生成一個新的SDWebImageManager
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader NS_DESIGNATED_INITIALIZER;
//下載圖片的關(guān)鍵方法,第一個參數(shù)圖片url,第二個參數(shù)設(shè)置下載多樣操作,第三個參數(shù)下載中進(jìn)度block,第四個參數(shù)下載完成后回調(diào)
- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock;
//緩存圖片根據(jù)指定的url和image
- (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url;
//取消所有當(dāng)前的operation
- (void)cancelAll;
//檢查是否有圖片正在下載
- (BOOL)isRunning;
//異步檢查圖片是否已經(jīng)緩存
- (void)cachedImageExistsForURL:(nullable NSURL *)url
completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
//檢查圖片是否緩存 在磁盤中
- (void)diskImageExistsForURL:(nullable NSURL *)url
completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
//給定一個url返回緩存的字符串key
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url;
@end
SDWebImageManager.m注解:
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
//是否取消當(dāng)前所有操作
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
//沒有參數(shù)取消回調(diào)
@property (copy, nonatomic, nullable) SDWebImageNoParamsBlock cancelBlock;
//執(zhí)行緩存的操作
@property (strong, nonatomic, nullable) NSOperation *cacheOperation;
@end
@interface SDWebImageManager ()
//緩存對象
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;
//下載對象
@property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader *imageDownloader;
//集合存儲所有下載失敗的圖片url
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;
//存儲正在執(zhí)行下載圖片操作的數(shù)組
@property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageCombinedOperation *> *runningOperations;
@end
@implementation SDWebImageManager
//生成一個SDWebImagemanager的單例
+ (nonnull instancetype)sharedManager {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
//初始化SDImageCache/SDWebImageDownloade
- (nonnull instancetype)init {
SDImageCache *cache = [SDImageCache sharedImageCache];
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
return [self initWithCache:cache downloader:downloader];
}
//初始化以及屬性綁定
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader {
if ((self = [super init])) {
_imageCache = cache;
_imageDownloader = downloader;
_failedURLs = [NSMutableSet new];
_runningOperations = [NSMutableArray new];
}
return self;
}
//根據(jù)URL獲取緩存中的key
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url {
if (!url) {
return @"";
}
if (self.cacheKeyFilter) {
return self.cacheKeyFilter(url);
} else {
return url.absoluteString;
}
}
//檢查緩存中是否緩存了當(dāng)前url對應(yīng)的圖片-先判斷內(nèi)存緩存、再判斷磁盤緩存
- (void)cachedImageExistsForURL:(nullable NSURL *)url
completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
NSString *key = [self cacheKeyForURL:url];
//判斷內(nèi)存緩存是否存在
BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil);
if (isInMemoryCache) {
// making sure we call the completion block on the main queue
dispatch_async(dispatch_get_main_queue(), ^{
if (completionBlock) {
completionBlock(YES);
}
});
return;
}
//判斷磁盤緩存中是否存在
[self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
// the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
if (completionBlock) {
completionBlock(isInDiskCache);
}
}];
}
//根據(jù)URL判斷磁盤緩存中是否存在圖片
- (void)diskImageExistsForURL:(nullable NSURL *)url
completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
NSString *key = [self cacheKeyForURL:url];
[self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
// the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
if (completionBlock) {
completionBlock(isInDiskCache);
}
}];
}
//進(jìn)行圖片下載操作
- (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
//completedBlock為nil,則觸發(fā)斷言,程序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;
}
//封裝下載操作的對象
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
BOOL isFailedUrl = NO;
if (url) {
//為了防止在多線程訪問出現(xiàn)問題,創(chuàng)建互斥鎖
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
}
//如果url為nil,或者沒有設(shè)置失敗url重新下載的配置且該url已經(jīng)下載失敗過,那么返回失敗的回調(diào)
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;
}
//創(chuàng)建互斥鎖,添加operation到數(shù)組中
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
NSString *key = [self cacheKeyForURL:url];
//使用緩存對象,根據(jù)key去尋找查找
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
//如果當(dāng)前操作被取消,則remove且return
if (operation.isCancelled) {
[self safelyRemoveOperationFromRunning:operation];
return;
}
//(如果沒有圖片緩存或者設(shè)置了重新刷新緩存)且調(diào)用代理允許下載圖片
if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
//如果有(其實是緩存的)并且調(diào)用者設(shè)置了重新刷新緩存,那么先把圖片結(jié)果回調(diào)出去,然后繼續(xù)去下載圖片再更新緩存
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:weakOperation 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ù)調(diào)用者傳進(jìn)來的option,來匹配設(shè)置了哪些,就給downloaderOptions賦值哪些option
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;
if (cachedImage && options & SDWebImageRefreshCached) {
// 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;
}
//在這里真正調(diào)用imageDownloader去下載圖片了
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) {
// 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:strongOperation completion:completedBlock error:error url:url];
if ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost
&& error.code != NSURLErrorNetworkConnectionLost) {
//下載失敗則添加圖片url到failedURLs集合
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
}
}
else {
//雖然下載失敗,但是如果設(shè)置了可以重新下載失敗的url則remove該url
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
//是否需要緩存在磁盤
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
//圖片下載成功并且判斷是否需要轉(zhuǎn)換圖片
} 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), ^{
//根據(jù)代理獲取轉(zhuǎn)換后的圖片
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
//如果轉(zhuǎn)換圖片存在且下載圖片操作已完成 則在緩存對象中存儲圖片
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];
}
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
});
} else {
//下載完成且有image則緩存圖片
if (downloadedImage && finished) {
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
}
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
//如果下載和緩存都完成了則刪除操作隊列中的operation
if (finished) {
[self safelyRemoveOperationFromRunning:strongOperation];
}
}];
@synchronized(operation) {
// Need same lock to ensure cancelBlock called because cancel method can be called in different queue
operation.cancelBlock = ^{
[self.imageDownloader cancel:subOperationToken];
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self safelyRemoveOperationFromRunning:strongOperation];
};
}
} else if (cachedImage) {
// 有圖片且線程沒有被取消,則返回有圖片的completedBlock
__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 {
// Image not in cache and download disallowed by delegate
//沒有在緩存中并且代理方法也不允許下載則回調(diào)失敗
__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;
}
//將圖片存入緩存
- (void)saveImageToCache:(nullable UIImage *)image forURL:(nullable NSURL *)url {
if (image && url) {
NSString *key = [self cacheKeyForURL:url];
[self.imageCache storeImage:image forKey:key toDisk:YES completion:nil];
}
}
//取消所有的下載操作
- (void)cancelAll {
@synchronized (self.runningOperations) {
NSArray<SDWebImageCombinedOperation *> *copiedOperations = [self.runningOperations copy];
[copiedOperations makeObjectsPerformSelector:@selector(cancel)];
[self.runningOperations removeObjectsInArray:copiedOperations];
}
}
//判斷當(dāng)前是否有下載圖片
- (BOOL)isRunning {
BOOL isRunning = NO;
@synchronized (self.runningOperations) {
isRunning = (self.runningOperations.count > 0);
}
return isRunning;
}
//線程安全的移除下載operation
- (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation {
@synchronized (self.runningOperations) {
if (operation) {
[self.runningOperations removeObject:operation];
}
}
}
- (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];
}
- (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);
}
});
}
@end
@implementation SDWebImageCombinedOperation
//set方法
- (void)setCancelBlock:(nullable SDWebImageNoParamsBlock)cancelBlock {
// check if the operation is already cancelled, then we just call the cancelBlock
if (self.isCancelled) {
if (cancelBlock) {
cancelBlock();
}
_cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes
} else {
_cancelBlock = [cancelBlock copy];
}
}
//SDWebImageOperation協(xié)議方法
- (void)cancel {
@synchronized(self) {
self.cancelled = YES;
if (self.cacheOperation) {
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
if (self.cancelBlock) {
self.cancelBlock();
self.cancelBlock = nil;
}
}
}
@end
下面是SDWebImageManager的關(guān)鍵節(jié)點流程圖:

3>SDImageCache
SDImageCache是SDWebImage的緩存中心。分三部分組成memory內(nèi)存緩存,disk硬盤緩存和無緩存組成。
SDImageCache.h文件注解:
typedef NS_ENUM(NSInteger, SDImageCacheType) {
無緩存類型
SDImageCacheTypeNone,
磁盤緩存
SDImageCacheTypeDisk,
內(nèi)存緩存
SDImageCacheTypeMemory
};
@interface SDImageCache : NSObject
#pragma mark - Properties
//緩存配置對象,包含所有配置項
@property (nonatomic, nonnull, readonly) SDImageCacheConfig *config;
//設(shè)置內(nèi)存容量大小
@property (assign, nonatomic) NSUInteger maxMemoryCost;
//設(shè)置內(nèi)存緩存最大值limit
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit;
#pragma mark - Singleton and initialization
//返回SDImageCache單例
+ (nonnull instancetype)sharedImageCache;
//根據(jù)特定的namespace返回一個新的緩存對象
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns;
//根據(jù)特定的namespace和directory返回一個新的緩存對象
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nonnull NSString *)directory NS_DESIGNATED_INITIALIZER;
#pragma mark - Cache paths
//生成緩存路徑
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace;
//添加一個只讀的緩存路徑
- (void)addReadOnlyCachePath:(nonnull NSString *)path;
#pragma mark - Store Ops
//根據(jù)key去異步緩存image,緩存在內(nèi)存和磁盤
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
//根據(jù)key去異步緩存image,toDisk未NO不存儲在磁盤
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
//根據(jù)key去異步緩存image,toDisk未NO不存儲在磁盤 多加一個imageData圖片data
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
//根據(jù)key去異步緩存image,緩存在磁盤
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key;
#pragma mark - Query and Retrieve Ops
//異步檢查圖片是否緩存在磁盤中
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
//在緩存中查詢對應(yīng)key的數(shù)據(jù)
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;
//在內(nèi)存緩存中查詢對應(yīng)key的圖片
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key;
//在磁盤緩存中查詢對應(yīng)key的圖片
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key;
//在緩存中查詢對應(yīng)key的圖片
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key;
#pragma mark - Remove Ops
//刪除緩存中指定key的圖片
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion;
//刪除緩存中指定key的圖片 磁盤是可選項
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion;
#pragma mark - Cache clean Ops
//清空所有的內(nèi)存緩存
- (void)clearMemory;
//異步清除所有的磁盤緩存
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion;
//異步清除所有的失效的緩存圖片-因為可以設(shè)定緩存時間,超過則失效
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;
#pragma mark - Cache Info
//得到磁盤緩存的大小size
- (NSUInteger)getSize;
//得到在磁盤緩存中圖片的數(shù)量
- (NSUInteger)getDiskCount;
//異步計算磁盤緩存的大小
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock;
#pragma mark - Cache Paths
//獲取給定key的緩存路徑 需要一個根緩存路徑
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path;
//獲取給定key的默認(rèn)緩存路徑
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key;
@end
SDImageCache.m注解:
// See https://github.com/rs/SDWebImage/pull/1141 for discussion
//自定義內(nèi)存緩存類
@interface AutoPurgeCache : NSCache
@end
@implementation AutoPurgeCache
- (nonnull instancetype)init {
self = [super init];
if (self) {
#if SD_UIKIT
//添加通知,當(dāng)受到內(nèi)存警告則移除所有的緩存對象
[[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
//內(nèi)聯(lián)函數(shù)獲得該圖片的緩存大小 注意乘以屏幕的比例
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
}
@interface SDImageCache ()
#pragma mark - Properties
//內(nèi)存緩存對象
@property (strong, nonatomic, nonnull) NSCache *memCache;
//磁盤緩存路徑
@property (strong, nonatomic, nonnull) NSString *diskCachePath;
//保存緩存路徑的數(shù)組
@property (strong, nonatomic, nullable) NSMutableArray<NSString *> *customPaths;
//執(zhí)行處理輸入輸出的隊列
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t ioQueue;
@end
@implementation SDImageCache {
NSFileManager *_fileManager;
}
#pragma mark - Singleton, init, dealloc
//生成單例SDImageCache
+ (nonnull instancetype)sharedImageCache {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
- (instancetype)init {
return [self initWithNamespace:@"default"];
}
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns {
NSString *path = [self makeDiskCachePath:ns];
return [self initWithNamespace:ns diskCacheDirectory:path];
}
//初始化磁盤緩存路徑和內(nèi)存緩存name
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nonnull NSString *)directory {
if ((self = [super init])) {
NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];
// Create IO serial queue
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
_config = [[SDImageCacheConfig alloc] init];
// Init the memory cache
_memCache = [[AutoPurgeCache alloc] init];
_memCache.name = fullNamespace;
// Init the disk cache
if (directory != nil) {
_diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
} else {
NSString *path = [self makeDiskCachePath:ns];
_diskCachePath = path;
}
dispatch_sync(_ioQueue, ^{
_fileManager = [NSFileManager new];
});
#if SD_UIKIT
// Subscribe to app events
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearMemory)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deleteOldFiles)
name:UIApplicationWillTerminateNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundDeleteOldFiles)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
#endif
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
SDDispatchQueueRelease(_ioQueue);
}
- (void)checkIfQueueIsIOQueue {
//dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)獲取當(dāng)前隊列的名字
//dispatch_queue_get_label獲取隊列的名字,如果隊列沒有名字,返回NULL
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");
}
}
#pragma mark - Cache paths
////添加一個只讀的緩存路徑
- (void)addReadOnlyCachePath:(nonnull NSString *)path {
if (!self.customPaths) {
self.customPaths = [NSMutableArray new];
}
if (![self.customPaths containsObject:path]) {
[self.customPaths addObject:path];
}
}
//獲取給定key的緩存路徑 需要一個根緩存路徑
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path {
NSString *filename = [self cachedFileNameForKey:key];
return [path stringByAppendingPathComponent:filename];
}
//獲取給定key的默認(rèn)緩存路徑
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key {
return [self cachePathForKey:key inPath:self.diskCachePath];
}
//根據(jù)key值生成文件名:采用MD5
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
const char *str = key.UTF8String;
if (str == NULL) {
str = "";
}
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
NSURL *keyURL = [NSURL URLWithString:key];
NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
r[11], r[12], r[13], r[14], r[15], ext.length == 0 ? @"" : [NSString stringWithFormat:@".%@", ext]];
return filename;
}
//生成磁盤路徑
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {
NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
return [paths[0] stringByAppendingPathComponent:fullNamespace];
}
#pragma mark - Store Ops
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
[self storeImage:image imageData:nil forKey:key toDisk:YES completion:completionBlock];
}
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
[self storeImage:image imageData:nil forKey:key toDisk:toDisk completion:completionBlock];
}
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
// if memory cache is enabled
//緩存到內(nèi)存
if (self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(image);
[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, use PNG format
//如果沒有data則采用png的格式進(jìn)行format
data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:SDImageFormatPNG];
}
[self storeImageDataToDisk:data forKey:key];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
} else {
if (completionBlock) {
completionBlock();
}
}
}
//利用key進(jìn)行緩存data
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
if (!imageData || !key) {
return;
}
[self checkIfQueueIsIOQueue];
//如果文件中不存在磁盤緩存路徑 則創(chuàng)建
if (![_fileManager fileExistsAtPath:_diskCachePath]) {
[_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
// get cache Path for image key 得到該key的緩存路徑
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// transform to NSUrl 將緩存路徑轉(zhuǎn)化為url
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
//將imageData存儲起來
[_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
// disable iCloud backup 如果調(diào)用者關(guān)閉icloud 關(guān)閉iCloud備份
if (self.config.shouldDisableiCloud) {
[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}
#pragma mark - Query and Retrieve Ops
//異步檢查圖片是否緩存在磁盤中
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
dispatch_async(_ioQueue, ^{
BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];
// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension
if (!exists) {
exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key].stringByDeletingPathExtension];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(exists);
});
}
});
}
//在內(nèi)存緩存中查詢對應(yīng)key的圖片
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
return [self.memCache objectForKey:key];
}
//在磁盤緩存中查詢對應(yīng)key的圖片 并且如果允許內(nèi)存緩存則在內(nèi)存中緩存
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
return diskImage;
}
//在緩存中查詢對應(yīng)key的圖片
- (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;
}
//根據(jù)key在磁盤緩存中搜索圖片data
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
NSString *defaultPath = [self defaultCachePathForKey:key];
NSData *data = [NSData dataWithContentsOfFile:defaultPath options:self.config.diskCacheReadingOptions error:nil];
if (data) {
return data;
}
// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension
data = [NSData dataWithContentsOfFile:defaultPath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
if (data) {
return data;
}
NSArray<NSString *> *customPaths = [self.customPaths copy];
for (NSString *path in customPaths) {
NSString *filePath = [self cachePathForKey:key inPath:path];
NSData *imageData = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];
if (imageData) {
return imageData;
}
// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension
imageData = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
if (imageData) {
return imageData;
}
}
return nil;
}
//根據(jù)key在磁盤緩存中搜索圖片dimage
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
if (data) {
UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:data];
//根據(jù)圖片的scale或圖片中的圖片組 重新計算返回一張新圖片
image = [self scaledImageForKey:key image:image];
if (self.config.shouldDecompressImages) {
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&data options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
}
return image;
} else {
return nil;
}
}
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
return SDScaledImageForKey(key, image);
}
//在緩存中查詢對應(yīng)key的圖片信息 包含image,diskData以及緩存的類型
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// First check the in-memory cache...如果內(nèi)存緩存包含圖片數(shù)據(jù)則回調(diào)結(jié)束
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
NSData *diskData = nil;
if (image.images) {//imageData都存儲在磁盤
diskData = [self diskImageDataBySearchingAllPathsForKey:key];
}
if (doneBlock) {
doneBlock(image, diskData, SDImageCacheTypeMemory);
}
return nil;
}
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
// do not call the completion if cancelled
return;
}
@autoreleasepool {
//到這里說明已經(jīng)來到磁盤緩存區(qū)獲取 然后回調(diào)結(jié)束
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
if (doneBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
}
});
return operation;
}
#pragma mark - Remove Ops
//刪除緩存中指定key的圖片
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion {
[self removeImageForKey:key fromDisk:YES withCompletion:completion];
}
//刪除緩存中指定key的圖片 磁盤是可選項
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion {
if (key == nil) {
return;
}
//如果內(nèi)存也緩存了,則刪除內(nèi)存緩存
if (self.config.shouldCacheImagesInMemory) {
[self.memCache removeObjectForKey:key];
}
if (fromDisk) {
dispatch_async(self.ioQueue, ^{
[_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
} else if (completion){
completion();
}
}
# pragma mark - Mem Cache settings
- (void)setMaxMemoryCost:(NSUInteger)maxMemoryCost {
self.memCache.totalCostLimit = maxMemoryCost;
}
- (NSUInteger)maxMemoryCost {
return self.memCache.totalCostLimit;
}
- (NSUInteger)maxMemoryCountLimit {
return self.memCache.countLimit;
}
- (void)setMaxMemoryCountLimit:(NSUInteger)maxCountLimit {
self.memCache.countLimit = maxCountLimit;
}
#pragma mark - Cache clean Ops
//清空所有的內(nèi)存緩存
- (void)clearMemory {
[self.memCache removeAllObjects];
}
//異步清除所有的磁盤緩存
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion {
dispatch_async(self.ioQueue, ^{
[_fileManager removeItemAtPath:self.diskCachePath error:nil];
[_fileManager createDirectoryAtPath:self.diskCachePath
withIntermediateDirectories:YES
attributes:nil
error:NULL];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
}
- (void)deleteOldFiles {
[self deleteOldFilesWithCompletionBlock:nil];
}
//異步清除所有失效的緩存圖片-因為可以設(shè)定緩存時間,超過則失效
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
//resourceKeys數(shù)組包含遍歷文件的屬性,NSURLIsDirectoryKey判斷遍歷到的URL所指對象是否是目錄,
//NSURLContentModificationDateKey判斷遍歷返回的URL所指項目的最后修改時間,NSURLTotalFileAllocatedSizeKey判斷URL目錄中所分配的空間大小
NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
// This enumerator prefetches useful properties for our cache files.
//利用目錄枚舉器遍歷指定磁盤緩存路徑目錄下的文件,從而我們活的文件大小,緩存時間等信息
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:resourceKeys
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];
//計算過期時間,默認(rèn)1周以前的緩存文件是過期失效
NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.config.maxCacheAge];
//保存遍歷的文件url
NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
//保存當(dāng)前緩存的大小
NSUInteger currentCacheSize = 0;
// Enumerate all of the files in the cache directory. This loop has two purposes:
//
// 1. Removing files that are older than the expiration date.
// 2. Storing file attributes for the size-based cleanup pass.
//保存刪除的文件url
NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
//遍歷目錄枚舉器,目的1刪除過期文件 2紀(jì)錄文件大小,以便于之后刪除使用
for (NSURL *fileURL in fileEnumerator) {
NSError *error;
//獲取指定url對應(yīng)文件的指定三種屬性的key和value
NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];
// Skip directories and errors.
//如果是文件夾則返回
if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
continue;
}
// Remove files that are older than the expiration date;
//獲取指定url文件對應(yīng)的修改日期
NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
//如果修改日期大于指定日期,則加入要移除的數(shù)組里
if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
[urlsToDelete addObject:fileURL];
continue;
}
// Store a reference to this file and account for its total size.
//獲取指定的url對應(yīng)的文件的大小,并且把url與對應(yīng)大小存入一個字典中
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
cacheFiles[fileURL] = resourceValues;
}
//刪除所有最后修改日期大于指定日期的所有文件
for (NSURL *fileURL in urlsToDelete) {
[_fileManager removeItemAtURL:fileURL error:nil];
}
// If our remaining disk cache exceeds a configured maximum size, perform a second
// size-based cleanup pass. We delete the oldest files first.
//如果當(dāng)前緩存的大小超過了默認(rèn)大小,則按照日期刪除,直到緩存大小<默認(rèn)大小的一半
if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) {
// Target half of our maximum cache size for this cleanup pass.
const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2;
// Sort the remaining cache files by their last modification time (oldest first).
//根據(jù)文件創(chuàng)建的時間排序
NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
}];
// Delete files until we fall below our desired cache size.
//迭代刪除緩存,直到緩存大小是默認(rèn)緩存大小的一半
for (NSURL *fileURL in sortedFiles) {
if ([_fileManager removeItemAtURL:fileURL error:nil]) {
NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;
if (currentCacheSize < desiredCacheSize) {
break;
}
}
}
}
//在主線程中回調(diào)
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}
#if SD_UIKIT
//應(yīng)用進(jìn)入后臺的時候,調(diào)用這個方法 然后清除過期圖片
- (void)backgroundDeleteOldFiles {
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return;
}
UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
[self deleteOldFilesWithCompletionBlock:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
#endif
#pragma mark - Cache Info
//得到磁盤緩存的大小size
- (NSUInteger)getSize {
__block NSUInteger size = 0;
dispatch_sync(self.ioQueue, ^{
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
for (NSString *fileName in fileEnumerator) {
NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
NSDictionary<NSString *, id> *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
size += [attrs fileSize];
}
});
return size;
}
//得到在磁盤緩存中圖片的數(shù)量
- (NSUInteger)getDiskCount {
__block NSUInteger count = 0;
dispatch_sync(self.ioQueue, ^{
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
count = fileEnumerator.allObjects.count;
});
return count;
}
//異步計算磁盤緩存的大小
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock {
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
dispatch_async(self.ioQueue, ^{
NSUInteger fileCount = 0;
NSUInteger totalSize = 0;
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:@[NSFileSize]
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];
for (NSURL *fileURL in fileEnumerator) {
NSNumber *fileSize;
[fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
totalSize += fileSize.unsignedIntegerValue;
fileCount += 1;
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(fileCount, totalSize);
});
}
});
}
@end
下圖是在SDWebImageManager中調(diào)用SDCacheImage查找緩存的關(guān)鍵流程圖:
此篇文章講解的是UIImageView+WebCache/UIView+WebCache,SDWebImageManager和SDImageCache三個關(guān)鍵類,有許多很值得我們?nèi)W(xué)習(xí)的點,例如:
1.善用接口分離原則-設(shè)計更好的對外調(diào)用API。
2.適當(dāng)做異常處理機(jī)制-這些異常處理可以避免消耗不必要的資源或者異常發(fā)生。例如SDWebImageManager中
if (!url) { return @"";}
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
3.增加互斥鎖-起到線程的保護(hù)作用。
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
4.多利用inline函數(shù)-提高程序的執(zhí)行效率。
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.巧妙利用封裝思想和分層概念,寫出更加平臺化的組件-例如我們?nèi)粘J褂肧D大多都是利用UIImageView+WebCache的API, 其實SDWebImageManager是完全可以抽出來單獨使用,不會因為跳過了UIImageView+WebCache沒了依賴而無法使用。如果項目是需要多人跨多部門合作,如果他人或其他部門需要調(diào)用你寫的一個很牛逼的功能,那這個平臺化就很重要了。
SDWebImage還有很多值得我們借鑒和學(xué)習(xí)的地方,需要大家細(xì)細(xì)研讀。下一篇會講解SDWebImageDownloader和SDWebImageDownloaderOperation。