SDWebImage原理探索

話說想要提高必須站在巨人的肩膀上郭怪,最近沒事就研究一下SDWebImage的源碼

SDWebImage 是我們經(jīng)常使用的一個(gè)異步圖片加載庫,在項(xiàng)目中使用SDWebImage來管理圖片加載相關(guān)操作可以極大地提高開發(fā)效率刊橘,讓我們更加專注于業(yè)務(wù)邏輯實(shí)現(xiàn)鄙才。

SDWebImage的實(shí)現(xiàn)流程
SDWebimage.png
SDWebimage目錄結(jié)構(gòu)
目錄結(jié)構(gòu).png
實(shí)現(xiàn)原理

SDWebImage 是由一個(gè) SDImageCache(一個(gè)處理緩存的類) 和 SDWebImageDownloader(負(fù)責(zé)下載網(wǎng)絡(luò)圖片) ,而 SDWebImageManager則是管理者將前兩者結(jié)合起來完成整個(gè)工作流程促绵。

1 攒庵、入口 setImageWithURL:placeholderImage:options: 會(huì)先把 placeholderImage顯示,然后SDWebImageManager 根據(jù) URL 開始處理圖片败晴。

2浓冒、進(jìn)入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交給 SDImageCache 從緩存查找圖片是否已經(jīng)下載 queryDiskCacheForKey:delegate:userInfo:.

3位衩、先從內(nèi)存圖片緩存查找是否有圖片裆蒸,如果內(nèi)存中已經(jīng)有圖片緩存熔萧,SDImageCacheDelegate回調(diào) imageCache:didFindImage:forKey:userInfo 到 SDWebImageManager糖驴。

4僚祷、SDWebImageManagerDelegate 回調(diào) webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示圖片。

5贮缕、如果內(nèi)存緩存中沒有辙谜,生成 NSInvocationOperation 添加到隊(duì)列開始從硬盤查找圖片是否已經(jīng)緩存。

6感昼、根據(jù) URLKey 在硬盤緩存目錄下嘗試讀取圖片文件装哆。這一步是在 NSOperation 進(jìn)行的操作,所以回主線程進(jìn)行結(jié)果回調(diào) notifyDelegate:定嗓。

7蜕琴、如果上一操作從硬盤讀取到了圖片,將圖片添加到內(nèi)存緩存中(如果空閑內(nèi)存過小宵溅,會(huì)先清空內(nèi)存緩存)凌简。SDImageCacheDelegate 回調(diào) imageCache:didFindImage:forKey:userInfo:。進(jìn)而回調(diào)展示圖片恃逻。

8雏搂、如果從硬盤緩存目錄讀取不到圖片,說明所有緩存都不存在該圖片寇损,需要下載圖片凸郑,回調(diào) imageCache:didNotFindImageForKey:userInfo:

9矛市、共享或重新生成一個(gè)下載器 SDWebImageDownloader 開始下載圖片芙沥。圖片下載由 NSURLConnection 來做,實(shí)現(xiàn)相關(guān) delegate 來判斷圖片下載中浊吏、下載完成和下載失敗憨愉。

10、connection:didReceiveData: 中利用ImageIO 做了按圖片下載進(jìn)度加載效果卿捎。

connectionDidFinishLoading: 數(shù)據(jù)下載完成后交給 SDWebImageDecoder 做圖片解碼處理配紫。

11、圖片解碼處理在一個(gè) NSOperationQueue 完成午阵,不會(huì)拖慢主線程 UI躺孝。如果有需要對(duì)下載的圖片進(jìn)行二次處理,最好也在這里完成底桂,效率會(huì)好很多植袍。

在主線程 notifyDelegateOnMainThreadWithInfo: 宣告解碼完成,imageDecoder:didFinishDecodingImage:userInfo: 回調(diào)給 SDWebImageDownloader籽懦。

imageDownloader:didFinishWithImage:回調(diào)給 SDWebImageManager告知圖片下載完成于个。

通知所有的downloadDelegates下載完成,回調(diào)給需要的地方展示圖片暮顺。

12厅篓、將圖片保存到 SDImageCache 中秀存,內(nèi)存緩存和硬盤緩存同時(shí)保存。寫文件到硬盤也在以單獨(dú)NSInvocationOperation完成羽氮,避免拖慢主線程或链。

SDImageCache 在初始化的時(shí)候會(huì)注冊(cè)一些消息通知骇扇,在內(nèi)存警告或退到后臺(tái)的時(shí)候清理內(nèi)存圖片緩存吊宋,應(yīng)用結(jié)束的時(shí)候清理過期圖片。

SDWebImage 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache讼溺,方便使用令宿。

SDWebImagePrefetcher 可以預(yù)先下載圖片叼耙,方便后續(xù)使用。

SDWebImageManager管理類

SDWebImageManager管理類中一共有12個(gè)枚舉方法粒没,3個(gè)回調(diào)的block旬蟋,2個(gè)代理方法,8個(gè)成員方法
12個(gè)枚舉方法

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    
    /**
     * 默認(rèn)情況下革娄,如果一個(gè)url在下載的時(shí)候失敗了倾贰,那么這個(gè)url會(huì)被加入黑名單,不會(huì)嘗試再次下載拦惋。如果使用該參數(shù)匆浙,則該URL不會(huì)被添加到黑名單中。意味著會(huì)對(duì)下載失敗的URL嘗試重新下載厕妖。
     * 此標(biāo)記取消黑名單
     */
    SDWebImageRetryFailed = 1 << 0, //失敗后嘗試重新下載
    
    /**
     * 默認(rèn)情況下首尼,在 UI 交互時(shí)也會(huì)啟動(dòng)圖像下載,此標(biāo)記取消這一特性
     * 會(huì)推遲到滾動(dòng)視圖停止?jié)L動(dòng)之后再繼續(xù)下載
     * 備注:NSURLConnection 的網(wǎng)絡(luò)下載事件監(jiān)聽的運(yùn)行循環(huán)模式是 NSDefaultRunLoopMode
     */
    SDWebImageLowPriority = 1 << 1, //低優(yōu)先級(jí)
   
    SDWebImageCacheMemoryOnly = 1 << 2, //只使用內(nèi)存緩存

    SDWebImageProgressiveDownload = 1 << 3, //漸進(jìn)式下載
    
    /**
     * 遵守 HTPP 響應(yīng)的緩存控制言秸,如果需要软能,從遠(yuǎn)程刷新圖像
     * 磁盤緩存將由 NSURLCache 處理,而不是 SDWebImage举畸,這會(huì)對(duì)性能有輕微的影響
     * 此選項(xiàng)用于處理URL指向圖片發(fā)生變化的情況
     * 如果緩存的圖像被刷新查排,會(huì)調(diào)用一次 completion block,并傳遞最終的圖像
     */
    SDWebImageRefreshCached = 1 << 4,   //刷新緩存
    

    SDWebImageContinueInBackground = 1 << 5,    //后臺(tái)下載
    
    SDWebImageHandleCookies = 1 << 6,   //處理保存在NSHTTPCookieStore中的cookies
    
 
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,     //允許不信任的 SSL 證書
    
    /**
     *  默認(rèn)情況下抄沮,圖像會(huì)按照添加到隊(duì)列中的順序被加載跋核,此標(biāo)記會(huì)將它們移動(dòng)到隊(duì)列前端被立即加載
     *  而不是等待當(dāng)前隊(duì)列被加載,因?yàn)榈却?duì)列加載會(huì)需要一段時(shí)間
     */
    SDWebImageHighPriority = 1 << 8,    //高優(yōu)先級(jí)(優(yōu)先下載)
    
    /**
     * 默認(rèn)情況下叛买,在加載圖像時(shí)砂代,占位圖像已經(jīng)會(huì)被加載。
     * 此標(biāo)記會(huì)延遲加載占位圖像率挣,直到圖像已經(jīng)完成加載
     */
    SDWebImageDelayPlaceholder = 1 << 9,    //延遲占位圖片
    
    SDWebImageTransformAnimatedImage = 1 << 10, //轉(zhuǎn)換動(dòng)畫圖像
    
    SDWebImageAvoidAutoSetImage = 1 << 11   //手動(dòng)設(shè)置圖像
};

3個(gè)block回調(diào)

//定義任務(wù)完成的block塊
typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL);
//定義任務(wù)結(jié)束的block塊
typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);
//定義緩存過濾器的block塊
typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);

2個(gè)代理

/**
 * 如果該圖片沒有緩存刻伊,那么下載
 *
 * @param imageManager:當(dāng)前的SDWebImageManager
 * @param imageURL:要下載圖片的URL地址
 *
 * @return 如果要下載的圖片在緩存中不存在,則返回NO,否則返回YES
 */
- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
/**

 * 允許在下載后立即將圖像轉(zhuǎn)換捶箱,并進(jìn)行磁盤和內(nèi)存緩存智什。
 *
 * @param imageManager 當(dāng)前的SDWebImageManager
 * @param image 要轉(zhuǎn)換你的圖片
 * @param imageURL 要轉(zhuǎn)換的圖片的URL地址
 *
 * @return 變換后的圖片對(duì)象
 */
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;

成員方法
該方法是管理類中最重要的方法

 /* 如果URL對(duì)應(yīng)的圖像在緩存中不存在,那么就下載指定的圖片 讼呢,否則返回緩存的圖像
 *
 * @param url 圖片的URL地址
 * @param options 指定此次請(qǐng)求策略的選項(xiàng)
 * @param progressBlock 圖片下載進(jìn)度的回調(diào)
 * @param completedBlock 操作完成后的回調(diào)
 *      此參數(shù)是必須的撩鹿,此block沒有返回值
 *      Image:請(qǐng)求的 UIImage谦炬,如果出現(xiàn)錯(cuò)誤悦屏,image參數(shù)是nil
 *      error:如果出現(xiàn)錯(cuò)誤,則error有值
 *      cacheType:`SDImageCacheType` 枚舉键思,標(biāo)示該圖像的加載方式
 *          SDImageCacheTypeNone:從網(wǎng)絡(luò)下載
 *          SDImageCacheTypeDisk:從本地緩存加載
 *          SDImageCacheTypeMemory:從內(nèi)存緩存加載
 *          finished:如果圖像下載完成則為YES础爬,如果使用 SDWebImageProgressiveDownload 選項(xiàng),同時(shí)只獲取到部分圖片時(shí)吼鳞,返回 NO
 *          imageURL:圖片的URL地址
 *
 * @return SDWebImageOperation對(duì)象看蚜,應(yīng)該是SDWebimageDownloaderOperation實(shí)例
 */
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBloc
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;

方法解釋

- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock {
    // 沒有completedblock,那么調(diào)用這個(gè)方法是毫無意義的
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

     //檢查用戶傳入的URL是否正確赔桌,如果該URL是NSString類型的供炎,那么嘗試轉(zhuǎn)換
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    //防止因參數(shù)類型錯(cuò)誤而導(dǎo)致應(yīng)用程序崩潰,判斷URL是否是NSURL類型的疾党,如果不是則直接設(shè)置為nil
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }

    //初始化一個(gè)SDWebImageCombinedOperationBlock塊
    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;

    BOOL isFailedUrl = NO;
    if (url) {
        @synchronized (self.failedURLs) {
            isFailedUrl = [self.failedURLs containsObject:url];
        }
    }
    //如果url不正確或者 選擇的下載策略不是『下載失敗嘗試重新下載』且該URL存在于黑名單中音诫,那么直接返回,回調(diào)任務(wù)完成block塊雪位,傳遞錯(cuò)誤信息
    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;
    }

    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
    
    //得到該URL對(duì)應(yīng)的緩存KEY
    NSString *key = [self cacheKeyForURL:url];
    //該方法查找URLKEY對(duì)應(yīng)的圖片緩存是否存在竭钝,查找完畢之后把該圖片(存在|不存在)和該圖片的緩存方法以block的方式傳遞
    //緩存情況查找完畢之后,在block塊中進(jìn)行后續(xù)處理(如果該圖片沒有緩存·下載|如果緩存存在|如果用戶設(shè)置了下載的緩存策略是刷新緩存如何處理等等)
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        if (operation.isCancelled) {
            [self safelyRemoveOperationFromRunning:operation];
            return;
        }

        if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            if (cachedImage && options & SDWebImageRefreshCached) {
               
                [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }

        
            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;
            }
            //核心方法:使用下載器雹洗,下載圖片
            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) {
                    //如果是取消操作香罐,就什么也不做
                } else if (error) {
                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
                    //如果下載失敗,則處理結(jié)束的回調(diào)时肿,在合適的情況下把對(duì)應(yīng)圖片的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) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    //是否要進(jìn)行磁盤緩存庇茫?
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                    if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                        
                    } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        //否則,如果下載圖片存在且(不是可動(dòng)畫圖片數(shù)組||下載策略為SDWebImageTransformAnimatedImage&&transformDownloadedImage方法可用)
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            
                             //在下載后立即將圖像轉(zhuǎn)換螃成,并進(jìn)行磁盤和內(nèi)存緩存
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];

                            if (transformedImage && finished) {
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                
                                [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
                            }
                            //在主線程中回調(diào)completedBlock
                            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                        });
                    } else {
                        if (downloadedImage && finished) {
                            //得到下載的圖片且已經(jīng)完成港令,則進(jìn)行緩存處理
                            [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];
                    }
                }

                if (finished) {
                    // 下載結(jié)束,移除隊(duì)列
                    [self safelyRemoveOperationFromRunning:strongOperation];
                }
            }];
            //取消操作
            operation.cancelBlock = ^{
                [self.imageDownloader cancel:subOperationToken];
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                [self safelyRemoveOperationFromRunning:strongOperation];
            };
        } else if (cachedImage) {
            //緩存
            __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 {
            
            __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;
}

其他方法

/**
 * 根據(jù)圖片的URL保存圖片到緩存
 *
 * @param image:緩存的圖片
 * @param url:該圖片的URL地址
 */
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
/**
 * 取消當(dāng)前所有操作
 */
- (void)cancelAll;
/**
 * 檢查一個(gè)或多個(gè)操作是否正在運(yùn)行
 */
- (BOOL)isRunning;
/*
 *  檢查圖像是否已經(jīng)被緩存,如果已經(jīng)緩存則返回YES
 *  @param url:圖片對(duì)應(yīng)的URL
 *
 *  @return 返回是否存在的BOOL值
 */
- (BOOL)cachedImageExistsForURL:(NSURL *)url;
/**
 *  檢查圖像是否存在磁盤緩存(此方法僅針對(duì)磁盤進(jìn)行檢查锈颗,只要存在就返回YES)
 *
 *  @param url 圖片的url
 *
 *  @return 是否存在(只檢查磁盤緩存)
 */
- (BOOL)diskImageExistsForURL:(NSURL *)url;
/**
 *  異步檢查圖像是否已經(jīng)有內(nèi)存緩存
 *
 *  @param URL             圖片對(duì)應(yīng)的URL
 *  @param completionBlock 當(dāng)任務(wù)執(zhí)行完畢之后調(diào)用的block
 *  @note  completionBlock始終在主隊(duì)列執(zhí)行
 */
- (void)cachedImageExistsForURL:(NSURL *)url
                     completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
 *  異步檢查圖像是否已經(jīng)有磁盤緩存
 *
 *  @param URL             圖片對(duì)應(yīng)的URL
 *  @param completionBlock 當(dāng)任務(wù)執(zhí)行完畢之后調(diào)用的block
 *  @note  completionBlock始終在主隊(duì)列執(zhí)行
 */
- (void)diskImageExistsForURL:(NSURL *)url
                   completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
 * 返回指定URL的緩存鍵值顷霹,就是URL字符串
 */
- (NSString *)cacheKeyForURL:(NSURL *)url;

SDWebImageDownloader下載類

SDWebImageDownloader的頭文件內(nèi)容比較少,主要是定義了一些基本參數(shù)如下載優(yōu)先級(jí)策略击吱、最大并發(fā)數(shù)淋淀、超時(shí)時(shí)間等

SDWebImageDownloader 中核心方法:下載圖片的操作
/*
 * 使用給定的 URL 創(chuàng)建 SDWebImageDownloader 異步下載器實(shí)例
 * 圖像下載完成或者出現(xiàn)錯(cuò)誤時(shí)會(huì)通知代理
 */
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                            options:(SDWebImageDownloaderOptions)options
                       progress:(SDWebImageDownloaderProgressBlock)progressBlock                                                                                    
                    completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
    __block SDWebImageDownloaderOperation *operation;
    __weak __typeof(self)wself = self;  //為了避免block的循環(huán)引用
    //處理進(jìn)度回調(diào)|完成回調(diào)等,如果該url在self.URLCallbacks并不存在,則調(diào)用createCallback block塊
    [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{
        
        //處理下載超時(shí)朵纷,如果沒有設(shè)置過則初始化為15秒
        NSTimeInterval timeoutInterval = wself.downloadTimeout;
        if (timeoutInterval == 0.0) {
            timeoutInterval = 15.0;
        }
        // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
        //根據(jù)給定的URL和緩存策略創(chuàng)建可變的請(qǐng)求對(duì)象炭臭,設(shè)置請(qǐng)求超時(shí)
        //請(qǐng)求策略:如果是SDWebImageDownloaderUseNSURLCache則使用NSURLRequestUseProtocolCachePolicy,否則使用NSURLRequestReloadIgnoringLocalCacheData
        /*
         NSURLRequestUseProtocolCachePolicy:默認(rèn)的緩存策略
            1)如果緩存不存在袍辞,直接從服務(wù)端獲取鞋仍。
            2)如果緩存存在,會(huì)根據(jù)response中的Cache-Control字段判斷下一步操作搅吁,如: Cache-Control字段為must-revalidata, 則詢問服務(wù)端該數(shù)據(jù)是否有更新威创,無更新的話直接返回給用戶緩存數(shù)據(jù),若已更新谎懦,則請(qǐng)求服務(wù)端.
         NSURLRequestReloadIgnoringLocalCacheData:忽略本地緩存數(shù)據(jù)肚豺,直接請(qǐng)求服務(wù)端。
         */
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
        
        //設(shè)置是否使用Cookies(采用按位與)
        /*
         關(guān)于cookies參考:http://blog.csdn.net/chun799/article/details/17206907
         */
        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
        //開啟HTTP管道界拦,這可以顯著降低請(qǐng)求的加載時(shí)間吸申,但是由于沒有被服務(wù)器廣泛支持,默認(rèn)是禁用的
        request.HTTPShouldUsePipelining = YES;
        
        //設(shè)置請(qǐng)求頭信息(過濾等)
        if (wself.headersFilter) {
            request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
        }
        else {
            request.allHTTPHeaderFields = wself.HTTPHeaders;
        }
        
        //核心方法:創(chuàng)建下載圖片的操作
        operation = [[wself.operationClass alloc] initWithRequest:request options:options progress:^(NSInteger receivedSize, NSInteger expectedSize) {
            SDWebImageDownloader *sself = wself;
            if (!sself) return;
            __block NSArray *callbacksForURL;
            dispatch_sync(sself.barrierQueue, ^{
                callbacksForURL = [sself.URLCallbacks[url] copy];
            });
            
            //遍歷callbacksForURL數(shù)組中的所有字典享甸,執(zhí)行SDWebImageDownloaderProgressBlock回調(diào)
            for (NSDictionary *callbacks in callbacksForURL) {
                //說明:SDWebImageDownloaderProgressBlock作者可能考慮到用戶拿到進(jìn)度數(shù)據(jù)后會(huì)進(jìn)行刷新處理截碴,因此在主線程中處理了回調(diào)
                dispatch_async(dispatch_get_main_queue(), ^{
                    SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
                    if (callback) callback(receivedSize, expectedSize);
                });
            }
        } completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
            SDWebImageDownloader *sself = wself;
            if (!sself) return;
            __block NSArray *callbacksForURL;
            
            dispatch_barrier_sync(sself.barrierQueue, ^{
                callbacksForURL = [sself.URLCallbacks[url] copy];
                
                //如果完成,那么把URL從URLCallbacks字典中刪除
                if (finished) {
                    [sself.URLCallbacks removeObjectForKey:url];
                }
            });
            
            //遍歷callbacksForURL數(shù)組中的所有字典蛉威,執(zhí)行SDWebImageDownloaderCompletedBlock回調(diào)
            for (NSDictionary *callbacks in callbacksForURL) {
                SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
                if (callback) callback(image, data, error, finished);
            }
        } cancelled:^{
            SDWebImageDownloader *sself = wself;
            if (!sself) return;
            
            //把當(dāng)前的url從URLCallbacks字典中移除
            dispatch_barrier_async(sself.barrierQueue, ^{
                [sself.URLCallbacks removeObjectForKey:url];
            });
        }];
        //設(shè)置是否需要解碼
        operation.shouldDecompressImages = wself.shouldDecompressImages;
        
        //身份認(rèn)證
        if (wself.urlCredential) {
            operation.credential = wself.urlCredential;
        } else if (wself.username && wself.password) {
            //設(shè)置 https 訪問時(shí)身份驗(yàn)證使用的憑據(jù)
            operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
        }
        
        //判斷下載策略是否是高優(yōu)先級(jí)的或低優(yōu)先級(jí)日丹,以設(shè)置操作的隊(duì)列優(yōu)先級(jí)
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        }
        
        //把下載操作添加到下載隊(duì)列中
        //該方法會(huì)調(diào)用operation內(nèi)部的start方法開啟圖片的下載任務(wù)
        [wself.downloadQueue addOperation:operation];
        
        //判斷任務(wù)的執(zhí)行優(yōu)先級(jí),如果是后進(jìn)先出瓷翻,則調(diào)整任務(wù)的依賴關(guān)系聚凹,優(yōu)先執(zhí)行當(dāng)前的(最后添加)任務(wù)
        if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
            [wself.lastAddedOperation addDependency:operation];
            
            wself.lastAddedOperation = operation;//設(shè)置當(dāng)前下載操作為最后一個(gè)操作
        }
    }];
    return operation;
}

SDImageCache緩存

SDWebImage實(shí)現(xiàn)了內(nèi)存緩存和磁盤緩存,內(nèi)存緩存是通過NSCache實(shí)現(xiàn)齐帚,磁盤緩存是通過NSFileManager來實(shí)現(xiàn)文件的存儲(chǔ)妒牙,磁盤緩存是異步實(shí)現(xiàn)的。
核心的方法就是對(duì)圖片緩存的增对妄、刪湘今、查的實(shí)現(xiàn)

/**
 * 使用指定的命名空間實(shí)例化一個(gè)新的緩存存儲(chǔ)
 * @param ns 緩存存儲(chǔ)使用的命名空間
 */
- (id)initWithNamespace:(NSString *)ns;
/**
 * 使用指定的命名空間實(shí)例化一個(gè)新的緩存存儲(chǔ)和目錄
 * @param  ns        緩存存儲(chǔ)使用的命名空間
 * @param  directory 緩存映像所在目錄
 */
- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory;
//設(shè)置磁盤緩存路徑
-(NSString *)makeDiskCachePath:(NSString*)fullNamespace;
/*
 * 如果希望在 bundle 中存儲(chǔ)預(yù)加載的圖像,可以添加一個(gè)只讀的緩存路徑
 * 讓 SDImageCache 從 Bundle 中搜索預(yù)先緩存的圖像
 * @param path 只讀緩存路徑(mainBundle中的全路徑)
 */
- (void)addReadOnlyCachePath:(NSString *)path;
/**

 * 使用指定的鍵將圖像保存到內(nèi)存和磁盤緩存
 *
 * @param image 要保存的圖片
 * @param key   唯一的圖像緩存鍵剪菱,通常是圖像的完整 URL
 */
- (void)storeImage:(UIImage *)image forKey:(NSString *)key;
/**
 * 使用指定的鍵將圖像保存到內(nèi)存和可選的磁盤緩存
 *
 * @param image  要保存的圖片
 * @param key    唯一的圖像緩存鍵摩瞎,通常是圖像的完整 URL
 * @param toDisk 如果是 YES,則將圖像緩存到磁盤
 */
- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk;
/**
 * 使用指定的鍵將圖像保存到內(nèi)存和可選的磁盤緩存
 *
 * @param image       要保存的圖像
 * @param recalculate 是否直接使用 imageData孝常,還是從 UIImage 重新構(gòu)造數(shù)據(jù)
 * @param imageData   從服務(wù)器返回圖像的二進(jìn)制數(shù)據(jù)旗们,表示直接保存到磁盤
                      而不是將給定的圖像對(duì)象轉(zhuǎn)換成一個(gè)可存儲(chǔ)/可壓縮的圖像格式,從而保留圖片質(zhì)量并降低 CPU 開銷
 * @param key         唯一的圖像緩存鍵构灸,通常是圖像的完整 URL
 * @param toDisk      如果是 YES上渴,則將圖像緩存到磁盤
 */
- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk;
/**
 * 異步查詢磁盤緩存
 *
 * @param key         保存圖像的唯一鍵
 * @param doneBlock   查詢結(jié)束后的回調(diào)
 */
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;
/**
 * 同步查詢內(nèi)存緩存
 *
 * @param key  保存圖像的唯一鍵
 */
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;
/**
 * 查詢內(nèi)存緩存之后同步查詢磁盤緩存
 *
 * @param key  保存圖像的唯一鍵
 */
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key;
/**
 * 同步從內(nèi)存和磁盤緩存刪除圖像
 *
 * @param key  保存圖像的唯一鍵
 */
- (void)removeImageForKey:(NSString *)key;
/**
 * 同步從內(nèi)存和磁盤緩存刪除圖像
 *
 * @param key        保存圖像的唯一鍵
 * @param completion 當(dāng)圖片被刪除后會(huì)調(diào)用該block塊
 */
- (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion;
/**
 * 同步從內(nèi)存和可選磁盤緩存刪除圖像
 *
 * @param key       保存圖像的唯一鍵
 * @param fromDisk  如果是 YES,則從磁盤刪除緩存
 */
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;
/**
 * 同步從內(nèi)存和可選磁盤緩存刪除圖像
 *
 * @param key         保存圖像的唯一鍵
 * @param fromDisk    如果是 YES,則從磁盤刪除緩存
 * @param completion  當(dāng)圖片被刪除后會(huì)調(diào)用該block塊
 */
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion;
/**
 * 刪除所有內(nèi)存緩存的圖像
 */
- (void)clearMemory;
/**
 * 刪除所有磁盤緩存的圖像稠氮。
 * @param completion 刪除操作后的塊代碼回調(diào)(可選)
 */
- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
/**
 * 刪除所有磁盤緩存的圖像
 * @see clearDiskOnCompletion:方法
 */
- (void)clearDisk;
/**
 * 從磁盤中刪除所有過期的緩存圖像曹阔。
 * @param completion 刪除操作后的塊代碼回調(diào)(可選)
 */
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock;
/**
 * 從磁盤中刪除所有過期的緩存圖像
 * @see cleanDiskWithCompletionBlock:方法
 */
- (void)cleanDisk;
/**
 * 獲得磁盤緩存占用空間
 */
- (NSUInteger)getSize;
/**
 * 獲得磁盤緩存圖像的個(gè)數(shù)
 */
- (NSUInteger)getDiskCount;
/**
 * 異步計(jì)算磁盤緩存的大小
 */
- (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock;
/**
 *  異步檢查圖像是否已經(jīng)在磁盤緩存中存在(不加載圖像)
 *  @param key              保存圖像的唯一鍵
 *  @param completionBlock  當(dāng)圖片被刪除后會(huì)調(diào)用該block塊
 *  @note  completionBlock總是在主線程
 */
- (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
 *  檢查圖像是否已經(jīng)在磁盤緩存中存在(不加載圖像)
 *
 *  @param key 保存圖像的唯一鍵
 *  @return 如果該圖片存在,則返回YES
 */
- (BOOL)diskImageExistsWithKey:(NSString *)key;
/**
 * 獲得指定 key 對(duì)應(yīng)的緩存路徑(需要指定緩存路徑的根目錄)
 *
 * @param key  鍵(可以調(diào)用cacheKeyForURL方法獲得)
 * @param path 緩存路徑根文件夾
 */
- (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path;
/**
 *  獲得指定 key 的默認(rèn)緩存路徑
 *
 *  @param key  鍵(可以調(diào)用cacheKeyForURL方法獲得)
 *
 *  @return 默認(rèn)緩存路徑
 */
- (NSString *)defaultCachePathForKey:(NSString *)key;

UIImageView+WebCache方法入口

UIImageView+WebCache是SDWebImage方法的入口隔披,其中最基本的方法是
所有設(shè)置圖片調(diào)用的方法最終都會(huì)調(diào)用這個(gè)方法

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

方法解析

//所有設(shè)置圖片調(diào)用的方法最終都會(huì)調(diào)用這個(gè)方法
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
    [self sd_cancelCurrentImageLoad];//取消先前的下載任務(wù)
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);//動(dòng)態(tài)添加屬性

    if (!(options & SDWebImageDelayPlaceholder)) {
        //如果SDWebImageDelayPlaceholder為nil
        dispatch_main_async_safe(^{
            self.image = placeholder;//設(shè)置占位圖
        });
    }
    //url不為nil
    if (url) {
        // check if activityView is enabled or not
        if ([self showActivityIndicatorView]) {
            //是否顯示進(jìn)度條
            [self addActivityIndicator];
        }
        //下載操作
        __weak __typeof(self)wself = self;
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
         
            [wself removeActivityIndicator];//移除進(jìn)度條
            if (!wself) return;//self是否被釋放
            dispatch_main_sync_safe(^{
                if (!wself) return;
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                {//不要自動(dòng)設(shè)置圖片赃份,那么調(diào)用Block傳入U(xiǎn)IImage對(duì)象
                    completedBlock(image, error, cacheType, url);
                    return;
                }
                else if (image) {
                    //設(shè)置圖片
                    wself.image = image;
                    [wself setNeedsLayout];
                } else {
                    if ((options & SDWebImageDelayPlaceholder)) {
                        //設(shè)置占位圖片
                        wself.image = placeholder;
                        [wself setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    //調(diào)用Block
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        //將生成的加載操作賦給UIView的自定義屬性
        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];//給添加的屬性賦值
    } else {
        dispatch_main_async_safe(^{
            //錯(cuò)誤處理
            [self removeActivityIndicator];
            NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
            if (completedBlock) {
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市奢米,隨后出現(xiàn)的幾起案子抓韩,更是在濱河造成了極大的恐慌,老刑警劉巖恃慧,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件园蝠,死亡現(xiàn)場離奇詭異渺蒿,居然都是意外死亡痢士,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門茂装,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怠蹂,“玉大人,你說我怎么就攤上這事少态〕遣啵” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵彼妻,是天一觀的道長嫌佑。 經(jīng)常有香客問我,道長侨歉,這世上最難降的妖魔是什么屋摇? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮幽邓,結(jié)果婚禮上炮温,老公的妹妹穿的比我還像新娘。我一直安慰自己牵舵,他們只是感情好柒啤,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著畸颅,像睡著了一般担巩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上没炒,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天涛癌,我揣著相機(jī)與錄音,去河邊找鬼。 笑死祖很,一個(gè)胖子當(dāng)著我的面吹牛笛丙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播假颇,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼胚鸯,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了笨鸡?” 一聲冷哼從身側(cè)響起姜钳,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎形耗,沒想到半個(gè)月后哥桥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡激涤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年拟糕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倦踢。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡送滞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辱挥,到底是詐尸還是另有隱情犁嗅,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布晤碘,位于F島的核電站褂微,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏园爷。R本人自食惡果不足惜宠蚂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望腮介。 院中可真熱鬧肥矢,春花似錦、人聲如沸叠洗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灭抑。三九已至十艾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間腾节,已是汗流浹背忘嫉。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國打工荤牍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人庆冕。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓康吵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親访递。 傳聞我的和親對(duì)象是個(gè)殘疾皇子晦嵌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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