搬好小板凳看SDWebImage源碼解析(一)

看了下距離上次寫簡書博客的時間,已經(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。


左移1位示意圖

依次類推:
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:

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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容