SDWebImage淺析


SDWebImage相信大家都很熟悉,畢竟大部分項(xiàng)目都會(huì)用。

SDWebImage特征

  • 提供了UIImageView,UIButton,MKAnnotationView的category來加載網(wǎng)絡(luò)圖片并且對網(wǎng)絡(luò)圖片進(jìn)行緩存管理
  • 異步下載圖片
  • 異步內(nèi)存+磁盤緩存,自動(dòng)緩存過期處理
  • 背景圖壓縮
  • 保證同一個(gè)URL不會(huì)重復(fù)幾次
  • 保證失效的URL不會(huì)一次又一次地重試
  • 保證主線程不會(huì)被阻塞
  • 使用GCD和ARC

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

SDWebImage使用

#import "UIImageView+WebCache.h"
/*
    不使用占位圖
*/
[imageView sd_setImageWithURL:[NSURL URLWithString:@"https://"]];
/*
    使用占位圖
*/
[imageView sd_setImageWithURL:[NSURL URLWithString:@"https://"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
/*
     使用占位圖,并且可以自己選擇SDWebImageOptions:
     //失敗重試
     SDWebImageRetryFailed = 1 << 0,
     //默認(rèn)情況下,圖片會(huì)在交互發(fā)生的時(shí)候下載(例如你滑動(dòng)tableview的時(shí)候),這個(gè)會(huì)禁止這個(gè)特性,導(dǎo)致的結(jié)果就是在scrollview減速的時(shí)候才會(huì)開始下載
     SDWebImageLowPriority = 1 << 1,
     //僅支持內(nèi)存緩存
     SDWebImageCacheMemoryOnly = 1 << 2,
     //邊下載邊顯示
     SDWebImageProgressiveDownload = 1 << 3,
     //刷新緩存(更換用戶頭像)
     SDWebImageRefreshCached = 1 << 4,
     //啟動(dòng)后臺(tái)下載
     SDWebImageContinueInBackground = 1 << 5,
     //通過設(shè)置處理存儲(chǔ)在NSHTTPCookieStore中的cookie
     SDWebImageHandleCookies = 1 << 6,
     //允許不安全的SSL證書
     SDWebImageAllowInvalidSSLCertificates = 1 << 7,
     //默認(rèn)情況下,image在加載的時(shí)候是按照他們在隊(duì)列中的順序加載的,這個(gè)會(huì)把他們移動(dòng)到隊(duì)列的前端,并且是立刻加載
     SDWebImageHighPriority = 1 << 8,
     //默認(rèn)情況下,占位圖會(huì)在圖片下載的時(shí)候顯示.這個(gè)開啟會(huì)延遲占位圖顯示的時(shí)間,等到圖片下載完成之后才顯示占位圖
     SDWebImageDelayPlaceholder = 1 << 9,
     //我們通常不會(huì)在動(dòng)畫圖像上調(diào)用transformDownloadedImage委托方法,因?yàn)榇蠖鄶?shù)轉(zhuǎn)換代碼會(huì)對它進(jìn)行轉(zhuǎn)換,使用這個(gè)來轉(zhuǎn)換它們
     SDWebImageTransformAnimatedImage = 1 << 10,
     //下載完成后手動(dòng)設(shè)置圖片,默認(rèn)是下載完成后自動(dòng)設(shè)置
     SDWebImageAvoidAutoSetImage = 1 << 11,
     //默認(rèn)情況下,圖像將根據(jù)其原始大小進(jìn)行解碼。 在iOS上,此標(biāo)志會(huì)將圖片縮小到與設(shè)備的受限內(nèi)存兼容的大小。如果設(shè)置該標(biāo)志,則會(huì)關(guān)閉縮小比例
     SDWebImageScaleDownLargeImages = 1 << 12
*/
[imageView sd_setImageWithURL:[NSURL URLWithString:@"https://"] placeholderImage:[UIImage imageNamed:@"placeholder.png"] options:SDWebImageRefreshCached];
/**
     可看到下載結(jié)果,block返回四個(gè)參數(shù)
     @param image 下載后的圖片
     @param error 錯(cuò)誤
     @param cacheType 緩存類型
     @param imageURL 下載圖片URL
*/
[imageView sd_setImageWithURL:[NSURL URLWithString:@"https://"] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
        /*
         SDImageCacheType類型:
         //沒有緩存,直接下載
         SDImageCacheTypeNone,
         //從磁盤緩存中獲得
         SDImageCacheTypeDisk,
         //從內(nèi)存緩存中獲得
         SDImageCacheTypeMemory
         */
    }];

這就是基本的使用方法了,很簡單,順便看下內(nèi)存緩存和磁盤緩存區(qū)別:

  • 內(nèi)存是指當(dāng)前程序的運(yùn)行空間,可用來臨時(shí)存儲(chǔ)文件,磁盤是指當(dāng)前程序的存儲(chǔ)空間,可用來永久存儲(chǔ)文件。
  • 內(nèi)存緩存速度快容量小,磁盤緩存容量大速度慢可持久化。

iOS采用沙盒機(jī)制,每個(gè)應(yīng)用程序只能在該程序創(chuàng)建的文件系統(tǒng)中讀取文件,不可以去其它地方訪問,此區(qū)域被稱為沙盒。默認(rèn)情況下,每個(gè)沙盒包含3個(gè)文件夾:Documents, Library 和tmp。

  • Documents:建議將所有的應(yīng)用程序數(shù)據(jù)文件寫入到這個(gè)目錄下。
  • Library:存儲(chǔ)程序的默認(rèn)設(shè)置或其它狀態(tài)信息
    Library/Caches:存放緩存文件
    Library/Preferences: 存放的是UserDefaults存儲(chǔ)的信息
  • tmp:創(chuàng)建臨時(shí)文件的地方

SDWebImage剖析

看下SDWebImageSequenceDiagram圖:


然后看下sd_internalSetImageWithURL

- (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 {
    //validOperationKey
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    //根據(jù)validOperationKey取消相關(guān)的下載任務(wù)
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    /*
     關(guān)聯(lián)對象
     源對象:self 關(guān)鍵字:imageURLKey  關(guān)聯(lián)的對象:url 一個(gè)關(guān)聯(lián)策略:OBJC_ASSOCIATION_RETAIN_NONATOMIC
     關(guān)聯(lián)策略
     OBJC_ASSOCIATION_ASSIGN = 0,      <指定一個(gè)弱引用關(guān)聯(lián)的對象>
     OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,<指定一個(gè)強(qiáng)引用關(guān)聯(lián)的對象>
     OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  <指定相關(guān)的對象復(fù)制>
     OBJC_ASSOCIATION_RETAIN = 01401,      <指定強(qiáng)參考>
     OBJC_ASSOCIATION_COPY = 01403    <指定相關(guān)的對象復(fù)制>
     */
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    if (!(options & SDWebImageDelayPlaceholder)) {
        //options存在并且options不是SDWebImageDelayPlaceholder,主線程設(shè)置占位圖
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });
    }
    //url存在
    if (url) {
        // check if activityView is enabled or not
        if ([self sd_showActivityIndicatorView]) {
            //是否顯示進(jìn)度條
            [self sd_addActivityIndicator];
        }
        
        __weak __typeof(self)wself = self;
        //執(zhí)行下載任務(wù)
        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;
            //移除進(jìn)度條
            [sself sd_removeActivityIndicator];
            //self是否被釋放
            if (!sself) { return; }
            //圖片下載完成或者options為SDWebImageAvoidAutoSetImage block回調(diào)結(jié)果
            BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
            //是否手動(dòng)設(shè)置圖片
            BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                      (!image && !(options & SDWebImageDelayPlaceholder)));
            SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
                //self是否被釋放
                if (!sself) { return; }
                //自動(dòng)設(shè)置
                if (!shouldNotSetImage) {
                    //標(biāo)記為需要重新布局,異步調(diào)用layoutIfNeeded刷新布局,不立即刷新,但layoutSubviews一定會(huì)被調(diào)用
                    [sself sd_setNeedsLayout];
                }
                //回調(diào)結(jié)果
                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
            //手動(dòng)設(shè)置圖片
            if (shouldNotSetImage) {
                dispatch_main_async_safe(callCompletedBlockClojure);
                return;
            }
            
            UIImage *targetImage = nil;
            NSData *targetData = nil;
            //圖片存在
            if (image) {
                // case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
                targetImage = image;
                targetData = data;
            //沒有圖片但是有占位圖
            } else if (options & SDWebImageDelayPlaceholder) {
                // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
                targetImage = placeholder;
                targetData = nil;
            }
            BOOL shouldUseGlobalQueue = NO;
            //是否有全局隊(duì)列(global_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è)置圖片
                [sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                dispatch_main_async_safe(callCompletedBlockClojure);
            });
        }];
        //
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } else {
        dispatch_main_async_safe(^{
            //移除進(jìn)度條
            [self sd_removeActivityIndicator];
            if (completedBlock) {
                //錯(cuò)誤
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

看下sd_cancelImageLoadOperationWithKey:

- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
    // Cancel in progress downloader from queue
    SDOperationsDictionary *operationDictionary = [self operationDictionary];
    //key獲取operations
    id operations = operationDictionary[key];
    //operations不為空
    if (operations) {
        //SDWebImageOperation數(shù)組
        if ([operations isKindOfClass:[NSArray class]]) {
            for (id <SDWebImageOperation> operation in operations) {
                if (operation) {
                    [operation cancel];
                }
            }
        } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
            //SDWebImageOperation協(xié)議
            [(id<SDWebImageOperation>) operations cancel];
        }
        //SDOperationsDictionary移除當(dāng)前key
        [operationDictionary removeObjectForKey:key];
    }
}

然后看下SDOperationsDictionary是個(gè)啥:

//定義字典key為string value為id類型()
typedef NSMutableDictionary<NSString *, id> SDOperationsDictionary;

//懶加載
- (SDOperationsDictionary *)operationDictionary {
    SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
    //存在返回
    if (operations) {
        return operations;
    }
    //不存在創(chuàng)建
    operations = [NSMutableDictionary dictionary];
    //關(guān)聯(lián)
    objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return operations;
}

loadImageWithURL看看是如何下載圖片的:

- (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,如果你想預(yù)取圖像,使用-[SDWebImagePrefetcher prefetchURLs]
    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.
    //判斷url類型
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    //url是否合法
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }
    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;
    //判斷url是否失敗過
    BOOL isFailedUrl = NO;
    if (url) {
        /*
         NSMutableSet:失敗url的一個(gè)集合
         @synchronized結(jié)構(gòu)所做的事情跟鎖(lock)類似,它防止不同的線程同時(shí)執(zhí)行同一段代碼。
         */
        @synchronized (self.failedURLs) {
            isFailedUrl = [self.failedURLs containsObject:url];
        }
    }
    //url為空或者 url下載失敗并且沒有設(shè)置下載失敗重試
    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;
    }
    //NSMutableArray<SDWebImageCombinedOperation *>
    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
    //根據(jù)url生成key
    NSString *key = [self cacheKeyForURL:url];
    //讀取緩存
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        //operation取消
        if (operation.isCancelled) {
            //下載任務(wù)移除
            [self safelyRemoveOperationFromRunning:operation];
            return;
        }

        if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            //圖片有緩存,options設(shè)置為SDWebImageRefreshCached 刷新緩存
            if (cachedImage && options & SDWebImageRefreshCached) {
                // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
                // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
                [self callCompletionBlockForOperation: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
            //下載圖片
            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;
                //operation不存在或者取消
                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è)置了失敗重試
                    if ((options & SDWebImageRetryFailed)) {
                        //將失敗的url從failedURLs集合中移除
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    //是否磁盤緩存
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
                    //options為刷新緩存,但是沒有下載的圖片
                    if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                        // 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];
                                // 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 {
                        //圖片下載完成
                        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];
                    }
                }

                if (finished) {
                    //移除operation
                    [self safelyRemoveOperationFromRunning:strongOperation];
                }
            }];
            //移除operation
            @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) {
            __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
            __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;
}

- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    //防止循環(huán)引用
    __weak SDWebImageDownloader *wself = self;

    return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
        __strong __typeof (wself) sself = wself;
        NSTimeInterval timeoutInterval = sself.downloadTimeout;
        //下載超時(shí)時(shí)間
        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
        //是否緩存
        NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
        //request
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
                                                                    cachePolicy:cachePolicy
                                                                timeoutInterval:timeoutInterval];
        //是否存儲(chǔ)Cookies
        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
        request.HTTPShouldUsePipelining = YES;
        //header
        if (sself.headersFilter) {
            request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
        }
        else {
            request.allHTTPHeaderFields = sself.HTTPHeaders;
        }
        SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
        operation.shouldDecompressImages = sself.shouldDecompressImages;
        //證書
        if (sself.urlCredential) {
            operation.credential = sself.urlCredential;
        } else if (sself.username && sself.password) {
            operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
        }
        //優(yōu)先級(jí)
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        }
        //addOperation
        [sself.downloadQueue addOperation:operation];
        /*
         //以隊(duì)列的方式,按照先進(jìn)先出的順序下載。這是默認(rèn)的下載順序
         SDWebImageDownloaderFIFOExecutionOrder,
         //以棧的方式,按照后進(jìn)先出的順序下載。
         SDWebImageDownloaderLIFOExecutionOrder
         */
        if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
            [sself.lastAddedOperation addDependency:operation];
            sself.lastAddedOperation = operation;
        }

        return operation;
    }];
}

緩存圖片storeImage:

- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    //image  key缺一不可
    if (!image || !key) {
        if (completionBlock) {
            completionBlock();
        }
        return;
    }
    // if memory cache is enabled
    //NSCache緩存(內(nèi)存緩存)
    if (self.config.shouldCacheImagesInMemory) {
        //圖片分辨率
        NSUInteger cost = SDCacheCostForImage(image);
        //存儲(chǔ)
        [self.memCache setObject:image forKey:key cost:cost];
    }
    //磁盤緩存
    if (toDisk) {
        //異步存儲(chǔ)
        dispatch_async(self.ioQueue, ^{
            //自動(dòng)釋放池
            @autoreleasepool {
                NSData *data = imageData;
                if (!data && image) {
                    // If we do not have any data to detect image format, use PNG 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();
        }
    }
}

- (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
    NSString *cachePathForKey = [self defaultCachePathForKey:key];
    // transform to NSUrl
    NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
    
    [_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
    
    // disable iCloud backup
    //禁用iCloud備份
    if (self.config.shouldDisableiCloud) {
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
}

如何從緩存中讀取圖片queryCacheOperationForKey:

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    //key必須不為空
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }

    // First check the in-memory cache...
    //首先從內(nèi)存中讀取
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        //磁盤讀取圖片data
        NSData *diskData = nil;
        if (image.images) {
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
        //回調(diào)(SDImageCacheTypeMemory)
        if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        return nil;
    }
    //圖片不在內(nèi)存中
    NSOperation *operation = [NSOperation new];
    //異步
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
        }
        //磁盤讀取
        @autoreleasepool {
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                //放入內(nèi)存
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }
            //回調(diào)(SDImageCacheTypeDisk)
            if (doneBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
        }
    });

    return operation;
}

到這里,基本就差不多了,SDWebImage緩沖用得是NSCatch和NSFileManager:

  • NSCache:是系統(tǒng)提供的一種類似于
    (NSMutableDictionary)的緩存,它和NSMutableDictionary的不同:
    1. NSCache具有自動(dòng)刪除的功能,以減少系統(tǒng)占用的內(nèi)存;
    2. NSCache是線程安全的,不需要加線程鎖;
    3. 鍵對象不會(huì)像 NSMutableDictionary 中那樣被復(fù)制(鍵不需要實(shí)現(xiàn) NSCopying 協(xié)議)。
  • NSFileManager:是iOS中的文件管理類。
    有興趣的可以自己再看看這方面的文檔。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,615評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,826評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,227評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,447評(píng)論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,992評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,807評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,001評(píng)論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評(píng)論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,243評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評(píng)論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,709評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,996評(píng)論 2 374