SDWebImage源碼解析(1)——總體架構(gòu)抹剩,Cache讀取

SDWebImage總體結(jié)構(gòu)

在平常的項目中司抱,對于加載網(wǎng)絡(luò)圖片蓖康,我們少不了用到SDWebImage铐炫。
僅需要調(diào)用UIImage分類的一行代碼(其余的加載圖片分類方法實際最終也會調(diào)到該方法):

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock

SDWebImage會自動為我們完成加載cache,更新cache以及下載圖片的功能蒜焊,而且僅需要調(diào)用這一個接口倒信。
SDWebImage是如何做到這些功能的呢?
源碼面前無秘密泳梆,那就讓我們一起來研究學習SDWebImage鳖悠。
首先榜掌,上一張SDWebImage的總體結(jié)構(gòu)圖:


我們在使用SDWebImage時,會調(diào)用UIImageView的分類方法乘综,而在分類方法里面憎账,實際上會調(diào)用單例實例
SDWebImageManager,而對應(yīng)每一個加載圖片操作(這里所謂的加載卡辰,指讀取緩存或下載圖片胞皱,并更新緩存),都有一個SDWebImageCombinedOperation對象九妈,注意這個對象類的名字中含有的‘Combined’反砌,表明這個Operation,其實是讀寫緩存萌朱,下載文件多種操作的一個Combine宴树。
SDWebImageManager中,又包含兩個只讀屬性
@property (strong, nonatomic, readonly) SDImageCache *imageCache;
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;
看名字就知道晶疼,imageCache負責Cache的讀寫及更新森渐,而imageDownloader則負責網(wǎng)絡(luò)下載圖片。
而在SDWebImageDownloader中冒晰,每一個下載操作,又對應(yīng)于一個SDWebImageDownloaderOperation對象竟块。

從上的結(jié)構(gòu)分析可以知道壶运,對于每個操作,SDWebImage實際會抽象出一個operation對象浪秘,這樣蒋情,就可以實現(xiàn)對于同時進行多個操作的一個管理,也就是對operation對象的管理耸携。

一張圖片是如何加載到UI的

看完上面SDWebImage的總體架構(gòu)圖棵癣,我們再結(jié)合實際代碼,來看看一個網(wǎng)絡(luò)圖片夺衍,是如何下載到本地的狈谊。
下面是UIImageView (WebCache)加載圖片的方法實現(xiàn):

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
    // step1. 先關(guān)掉之前的下載
    [self sd_cancelCurrentImageLoad];
    
    // step2. 利用runtime, 存儲當前下載的URL
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    if (!(options & SDWebImageDelayPlaceholder)) { // step3. 設(shè)置placeholder
        dispatch_main_async_safe(^{  // dispatch_main_async_safe 宏 用于確保在mainqueue 上執(zhí)行block
            self.image = placeholder;
        });
    }
    
    // step4. 進入圖片 下載/讀緩存 處理
    if (url) {

        // check if activityView is enabled or not
        if ([self showActivityIndicatorView]) {
            [self addActivityIndicator];
        }

        __weak __typeof(self)wself = self;
        // step4.1 調(diào)用 SDWebImageManager 單例下載
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            [wself removeActivityIndicator];
            if (!wself) return;  // 由于是block回調(diào) 這時weakself 可能已經(jīng)釋放,若釋放沟沙,則return河劝,不做任何回調(diào)處理
            dispatch_main_sync_safe(^{
                if (!wself) return;
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                {
                    completedBlock(image, error, cacheType, url);
                    return;
                }
                else if (image) {
                    wself.image = image;
                    [wself setNeedsLayout];
                } else {
                    if ((options & SDWebImageDelayPlaceholder)) {
                        wself.image = placeholder;
                        [wself setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        
        // step4.2 保存 當前下載所對應(yīng)的operation(目前operation僅支持cancel 接口)
        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
    } else { // 如果圖片url 為nil,則直接返回錯誤
        dispatch_main_async_safe(^{
            [self removeActivityIndicator];
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

代碼通過注釋矛紫,也不難理解赎瞎。SDWebImage加載圖片,分為如下幾個步驟:

  1. 先關(guān)閉之前的下載
  2. 將當前下載圖片的url作為UIImageView的屬性(利用runtime實現(xiàn))
  3. 如果有placeholder颊咬,則直接設(shè)置當前image為placeholder
  4. 調(diào)用SDWebImageManager單實例的downloadImageWithURL方法务甥,加載圖片(這里說加載而不是下載牡辽,是因為最終圖片來源可能是緩存),并返回一個id<SDWebImageOperation>對象(其實是一個SDWebImageCombinedOperation對象)敞临,代表當前的加載圖片操作态辛。
  5. 將id<SDWebImageOperation>對象保存,當需要取消加載時哟绊,僅需要調(diào)用id<SDWebImageOperation>協(xié)議方法cancel因妙。
  6. 在SDWebImageManger的downloadImageWithURL方法的回調(diào)block中,如果加載image圖片成功票髓,則設(shè)置UIImageIView的image屬性攀涵,并調(diào)用completedBlock,若失敗洽沟,則在completedBlock中返回失敗error以故。
    以上就是通過SDWebImage加載圖片的整個流程,到目前為止裆操,流程很清楚怒详。
    我們可以發(fā)現(xiàn),這里的核心邏輯踪区,就在于調(diào)用了SDWebImageManger的downloadImageWithURL方法昆烁。那么,我們接下來看看SDWebImageManger的代碼缎岗。

SDWebImageManager

先看SDWebImageManager的頭文件:

/**
 * The SDWebImageManager is the class behind the UIImageView+WebCache category and likes.
 * It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache).
 * You can use this class directly to benefit from web image downloading with caching in another context than
 * a UIView.
 *
 * Here is a simple example of how to use SDWebImageManager:
 *
 * @code

SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:imageURL
                      options:0
                     progress:nil
                    completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                        if (image) {
                            // do something with image
                        }
                    }];

 * @endcode
 */
@interface SDWebImageManager : NSObject

@property (weak, nonatomic) id <SDWebImageManagerDelegate> delegate;

@property (strong, nonatomic, readonly) SDImageCache *imageCache;
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;

/**
 * The cache filter is a block used each time SDWebImageManager need to convert an URL into a cache key. This can
 * be used to remove dynamic part of an image URL.
 *
 * The following example sets a filter in the application delegate that will remove any query-string from the
 * URL before to use it as a cache key:
 *
 * @code

[[SDWebImageManager sharedManager] setCacheKeyFilter:^(NSURL *url) {
    url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path];
    return [url absoluteString];
}];

 * @endcode
 */
@property (nonatomic, copy) SDWebImageCacheKeyFilterBlock cacheKeyFilter;

/**
 * Returns global SDWebImageManager instance.
 *
 * @return SDWebImageManager shared instance
 */
+ (SDWebImageManager *)sharedManager;

/**
 * Allows to specify instance of cache and image downloader used with image manager.
 * @return new instance of `SDWebImageManager` with specified cache and downloader.
 */
- (instancetype)initWithCache:(SDImageCache *)cache downloader:(SDWebImageDownloader *)downloader;

/**
 * Downloads the image at the given URL if not present in cache or return the cached version otherwise.
 *
 * @param url            The URL to the image
 * @param options        A mask to specify options to use for this request
 * @param progressBlock  A block called while image is downloading
 * @param completedBlock A block called when operation has been completed.
 *
 *   This parameter is required.
 * 
 *   This block has no return value and takes the requested UIImage as first parameter.
 *   In case of error the image parameter is nil and the second parameter may contain an NSError.
 *
 *   The third parameter is an `SDImageCacheType` enum indicating if the image was retrieved from the local cache
 *   or from the memory cache or from the network.
 *
 *   The last parameter is set to NO when the SDWebImageProgressiveDownload option is used and the image is 
 *   downloading. This block is thus called repeatedly with a partial image. When image is fully downloaded, the
 *   block is called a last time with the full image and the last parameter set to YES.
 *
 * @return Returns an NSObject conforming to SDWebImageOperation. Should be an instance of SDWebImageDownloaderOperation
 */
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;

/**
 * Saves image to cache for given URL
 *
 * @param image The image to cache
 * @param url   The URL to the image
 *
 */

- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;

/**
 * Cancel all current operations
 */
- (void)cancelAll;

/**
 * Check one or more operations running
 */
- (BOOL)isRunning;

/**
 *  Check if image has already been cached
 *
 *  @param url image url
 *
 *  @return if the image was already cached
 */
- (BOOL)cachedImageExistsForURL:(NSURL *)url;

/**
 *  Check if image has already been cached on disk only
 *
 *  @param url image url
 *
 *  @return if the image was already cached (disk only)
 */
- (BOOL)diskImageExistsForURL:(NSURL *)url;

/**
 *  Async check if image has already been cached
 *
 *  @param url              image url
 *  @param completionBlock  the block to be executed when the check is finished
 *  
 *  @note the completion block is always executed on the main queue
 */
- (void)cachedImageExistsForURL:(NSURL *)url
                     completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;

/**
 *  Async check if image has already been cached on disk only
 *
 *  @param url              image url
 *  @param completionBlock  the block to be executed when the check is finished
 *
 *  @note the completion block is always executed on the main queue
 */
- (void)diskImageExistsForURL:(NSURL *)url
                   completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;


/**
 *Return the cache key for a given URL
 */
- (NSString *)cacheKeyForURL:(NSURL *)url;

@end

注釋很多静尼,其實真正的代碼并不多。注意到開頭的一段注釋(結(jié)合自己理解簡單翻譯一下):

SDWebImageManager為UIImageView+WebCache加載圖片提供了核心實現(xiàn)传泊。它其實是管理了圖片異步的下載以及緩存處理鼠渺,同時,我們可以直接使用SDWebImageManager來加載網(wǎng)絡(luò)圖片眷细,而不僅僅是將其用于UIImageView中拦盹。所以,以后我們想下載網(wǎng)絡(luò)圖片溪椎,而不僅僅是為了做UI展示時普舆,可以直接使用SDWebImageManager來下載圖片以及緩存管理。

在這里池磁,我們還是先關(guān)注圖片是如何加載的奔害,除掉其他無關(guān)的代碼,SDWebImageManager可以簡化為:

@interface SDWebImageManager : NSObject

@property (weak, nonatomic) id <SDWebImageManagerDelegate> delegate;

@property (strong, nonatomic, readonly) SDImageCache *imageCache;  // 處理緩存相關(guān)
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;   // 處理圖片下載
@property (nonatomic, copy) SDWebImageCacheKeyFilterBlock cacheKeyFilter;  // 用于過濾處理圖片的url(這里可先不關(guān)注)
// 表明我是一個單例
+ (SDWebImageManager *)sharedManager;  

- (instancetype)initWithCache:(SDImageCache *)cache downloader:(SDWebImageDownloader *)downloader;

// 核心加載圖片方法 downloadImageWithURL
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;

SDWebImageManager的利用downloadImageWithURL方法加載圖片地熄。我們就來分析它的源代碼:

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
    // Invoking this method without a completedBlock is pointless
    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;

    // 如果是曾經(jīng)失敗的url 且當前的options不需重新嘗試失敗的鏈接华临,則直接返回錯誤
    BOOL isFailedUrl = NO;
    @synchronized (self.failedURLs) {
        isFailedUrl = [self.failedURLs containsObject:url];
    }
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        dispatch_main_sync_safe(^{
            NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
            completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
        });
        return operation;
    }

    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
    NSString *key = [self cacheKeyForURL:url];
    
    // step1. 先讀取緩存
    operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
        
        // 讀取緩存結(jié)束(可能有緩存, 也可能沒有)
        /////////////////////////////////////////////
        
        if (operation.isCancelled) {
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }

            return;
        }
        
        //// 緩存沒有或必須刷新緩存等端考, 走network download路線
        ///////////////////////////////////////
        if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            if (image && options & SDWebImageRefreshCached) {
                dispatch_main_sync_safe(^{
                    // 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.
                    completedBlock(image, nil, cacheType, YES, url);
                });
            }

            // download if no image or requested to refresh anyway, and download allowed by delegate
            /////////////////////////////////////////////////////////////////////
            // step1.  將外部傳入的SDWebImageOptions 轉(zhuǎn)化為 SDWebImageDownloaderOptions
            SDWebImageDownloaderOptions downloaderOptions = 0;
            if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
            if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
            if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
            if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
            if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
            if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
            if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
            if (image && options & SDWebImageRefreshCached) {  // 特殊處理一下 SDWebImageRefreshCached 請求的options
                // 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;
            }
            
            // step2. 利用SDWebImageDownloader開始下載 并返回一個 id <SDWebImageOperation>subOperation 代表當前下載operation(這里利用operation的cancel block對subOperaton 進行retain)
            id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, 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) {
                    dispatch_main_sync_safe(^{
                        if (strongOperation && !strongOperation.isCancelled) {
                            completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
                        }
                    });

                    if (   error.code != NSURLErrorNotConnectedToInternet
                        && error.code != NSURLErrorCancelled
                        && error.code != NSURLErrorTimedOut
                        && error.code != NSURLErrorInternationalRoamingOff
                        && error.code != NSURLErrorDataNotAllowed
                        && error.code != NSURLErrorCannotFindHost
                        && error.code != NSURLErrorCannotConnectToHost) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    
                    // 下載成功雅潭,做cache
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                    if (options & SDWebImageRefreshCached && image && !downloadedImage) {  // 如果download是因為refresh cache引起揭厚,而又沒有真正的download image下來,什么也不做
                        // Image refresh hit the NSURLCache cache, do not call the completion block
                    }
                    else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];

                            if (transformedImage && finished) {
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
                            }

                            dispatch_main_sync_safe(^{
                                if (strongOperation && !strongOperation.isCancelled) {
                                    completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
                                }
                            });
                        });
                    }
                    else {
                        if (downloadedImage && finished) {
                            [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
                        }
                        // 回調(diào)
                        dispatch_main_sync_safe(^{
                            if (strongOperation && !strongOperation.isCancelled) {
                                completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
                            }
                        });
                    }
                }
                // 如果結(jié)束扶供,則刪除該SDWebImageCombinedOperation
                if (finished) {
                    @synchronized (self.runningOperations) {
                        if (strongOperation) {
                            [self.runningOperations removeObject:strongOperation];
                        }
                    }
                }
            }];
            
            // step3. 設(shè)置operation的cancel block
            operation.cancelBlock = ^{
                [subOperation cancel];
                
                @synchronized (self.runningOperations) {
                    
                    __strong __typeof(weakOperation) strongOperation = weakOperation;
                    if (strongOperation) {
                        [self.runningOperations removeObject:strongOperation];
                    }
                }
            };
        }
        else if (image) {
            dispatch_main_sync_safe(^{
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if (strongOperation && !strongOperation.isCancelled) {
                    completedBlock(image, nil, cacheType, YES, url);
                }
            });
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }
        }
        else {
            // Image not in cache and download disallowed by delegate
            dispatch_main_sync_safe(^{
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if (strongOperation && !weakOperation.isCancelled) {
                    completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
                }
            });
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }
        }
    }];

    return operation;
}

代碼的邏輯大致分為三部分:

  1. 先判斷image的URL是否合法
    2.若URL合法筛圆,則先讀取緩存
    3.若緩存沒有該文件,則在網(wǎng)絡(luò)上下載

通過注釋椿浓,應(yīng)該也不難理解太援。
略過檢查URL合法性的代碼,我們首先來看SDWebImage是如何讀取Cache的扳碍。
也就是
SDImageCache 類
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock; 方法提岔。

SDImageCache

我們先看queryDiskCacheForKey的方法聲明:

typedef NS_ENUM(NSInteger, SDImageCacheType) {
    /**
     * The image wasn't available the SDWebImage caches, but was downloaded from the web.
     */
    SDImageCacheTypeNone = 1,
    /**
     * The image was obtained from the disk cache.
     */
    SDImageCacheTypeDisk,
    /**
     * The image was obtained from the memory cache.
     */
    SDImageCacheTypeMemory
};

typedef void(^SDWebImageQueryCompletedBlock)(UIImage *image, SDImageCacheType cacheType);
/**
 * Query the disk cache asynchronously.
 *
 * @param key The unique key used to store the wanted image
 */
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;

  1. queryDiskCacheForKey的返回值為一個NSOperation對象,外部可以通過該對象取消cache的查找過程笋敞。
    注意碱蒙,這里SDWebImage多次使用了該思路(加載Image,下載Image)夯巷,通過同步的返回一個代表當前操作的對象赛惩,來對異步操作進行管理。
  2. 參數(shù)接受一個URL MD5值作為Key查找Cache趁餐。
  3. 在回調(diào)block中喷兼,返回UIImage,并用SDImageCacheType來表明緩存文件的來源:沒有后雷,內(nèi)存褒搔,磁盤。
    這里喷面,我們也可以得知SDWebImage的cache策略,先讀內(nèi)存走孽,沒有命中惧辈,才去讀磁盤。

我們再來看他的具體實現(xiàn)

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
    if (!doneBlock) {
        return nil;
    }

    if (!key) {
        doneBlock(nil, SDImageCacheTypeNone);
        return nil;
    }

    // First check the in-memory cache...
    UIImage *image = [self imageFromMemoryCacheForKey:key];  // NSCache is threadsafe, don't need lock
    if (image) {
        doneBlock(image, SDImageCacheTypeMemory);
        return nil;
    }

    // Second check disk cache...
    NSOperation *operation = [NSOperation new]; // 這個NSOperation 對象的意義在于向外提供了cancel讀取disk cache的對象
    dispatch_async(self.ioQueue, ^{ // aync call. Note there sync io operation by serial queue
        if (operation.isCancelled) {
            return;
        }

        @autoreleasepool {
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

            dispatch_async(dispatch_get_main_queue(), ^{  // return to main queue
                doneBlock(diskImage, SDImageCacheTypeDisk);
            });
        }
    });

    return operation;
}
  1. First check in-memory cahce.
    這里的memory cahce用了繼承自系統(tǒng)的NSCache的AutoPurgeCache對象磕瓷。該對象在init時會注冊監(jiān)聽系統(tǒng)的
    UIApplicationDidReceiveMemoryWarningNotification 通知盒齿。
    當系統(tǒng)內(nèi)存告警時,會自動清除所有的memory cache:
 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  1. Second check disk cache困食。
    這里注意到边翁,之前都是同步的返回nil,而在讀取disk緩存時硕盹,才返回一個operation對象符匾。這是因為之前的讀取內(nèi)存cache都是同步的,而這里disk讀取Cache時異步的瘩例,為了為異步操作做管理啊胶,才需要想外返回一個operation對象以便隨時可以cancel該異步操作甸各。
    在讀取disk cache時,為了防止多線程同時讀寫一個文件焰坪,這里用到了
    串行隊列
    self.ioQueue
    來串行化io操作趣倾。這是我們可以借鑒的地方,利用串行隊列而非lock來保證線程安全某饰。

  2. 當從disk讀取緩存后儒恋,再將disk緩存存入memory cache。

  3. 調(diào)用done block黔漂,將UIImage的cache(如果有的話)诫尽,以及Cache來源返回。

SDWebImage的Cache策略

那么瘟仿,SDWebImage通過SDWebCache讀取Cache的流程如下:


在下一篇文章中箱锐,我會接著和大家一起分析,當Cache未命中時劳较,SDWebImage是如何在網(wǎng)絡(luò)下載圖片驹止,并更新Cahce的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末观蜗,一起剝皮案震驚了整個濱河市臊恋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌墓捻,老刑警劉巖抖仅,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異砖第,居然都是意外死亡撤卢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門梧兼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來放吩,“玉大人,你說我怎么就攤上這事羽杰《勺希” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵考赛,是天一觀的道長惕澎。 經(jīng)常有香客問我,道長颜骤,這世上最難降的妖魔是什么唧喉? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上欣喧,老公的妹妹穿的比我還像新娘腌零。我一直安慰自己,他們只是感情好唆阿,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布益涧。 她就那樣靜靜地躺著,像睡著了一般驯鳖。 火紅的嫁衣襯著肌膚如雪闲询。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天浅辙,我揣著相機與錄音扭弧,去河邊找鬼。 笑死记舆,一個胖子當著我的面吹牛鸽捻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泽腮,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼御蒲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了诊赊?” 一聲冷哼從身側(cè)響起厚满,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎碧磅,沒想到半個月后碘箍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡鲸郊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年丰榴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秆撮。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡多艇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出像吻,到底是詐尸還是另有隱情,我是刑警寧澤复隆,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布拨匆,位于F島的核電站,受9級特大地震影響挽拂,放射性物質(zhì)發(fā)生泄漏惭每。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望台腥。 院中可真熱鬧宏赘,春花似錦、人聲如沸黎侈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽峻汉。三九已至贴汪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間休吠,已是汗流浹背扳埂。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瘤礁,地道東北人阳懂。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像柜思,于是被迫代替她去往敵國和親岩调。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容