SDWebImage源碼流程分析

流程概括

image.png

在使用SDWebImage時,我們總會先調用sd_setImageWithURL等一系列方法。然后我們一層一層的探索下去時發現,歸根結底都是在調用UI賦值器中的sd_internalSetImageWithURL方法。這個方法的作用是利用圖片加載器的loadImageWithURL方法,將加載好的圖片賦值給UI。而圖片加載器,則先利用存儲器queryCacheOperationForKey查找圖片,如果沒有找到,就利用下載器downloadImageWithURL下載圖片。存儲器會先去緩存中讀取,如果沒找到,就從磁盤中讀?。ǘ壘彺妫?。下載器會把這些下載信息交給下載任務,并添加在下載隊列里,最多有6個下載任務可以同時執行。小毅接下來將對流程牽扯到的這四個方法進行講解。

一、 sd_internalSetImageWithURL

在URL正常的情況下,用加載器加載好的圖片給UI賦值
1、程序主流程


image.png

2、源碼解析

- (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 {

NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//如果沒有設置Placeholder延遲賦值,那就先把placeholder賦值給UI
if (!(options & SDWebImageDelayPlaceholder)) {
    dispatch_main_async_safe(^{
        [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
    });
}

//如果URL有值
if (url) {
    //看看是否要顯示ActivityIndicator
    if ([self sd_showActivityIndicatorView]) {
        [self sd_addActivityIndicator];
    }
    //有URL就去加載圖片
    __weak __typeof(self)wself = self;
    id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
        __strong __typeof (wself) sself = wself;
        [sself sd_removeActivityIndicator];
        //如果對象被釋放掉了就return
        if (!sself) {
            return;
        }
        dispatch_main_async_safe(^{
            //切換到主線程時,如果對象被釋放了就return
            if (!sself) {
                return;
            }
            //如果加載到圖片了,并且用戶設置了不讓自動給UI賦值,那就返回給你讓你自己看著辦
            if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                completedBlock(image, error, cacheType, url);
                return;
            //如果加載到了圖片,就給UI賦值
            } else if (image) {
                [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                [sself sd_setNeedsLayout];
            //如果沒加載到,就用placeholder賦值
            } else {
                if ((options & SDWebImageDelayPlaceholder)) {
                    [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                    [sself sd_setNeedsLayout];
                }
            }
            //順便返回給你
            if (completedBlock && finished) {
                completedBlock(image, error, cacheType, url);
            }
        });
    }];
    [self sd_setImageLoadOperation:operation forKey:validOperationKey];
//如果url是nil,就可以給你拋錯啦~
} else {
    dispatch_main_async_safe(^{
        [self sd_removeActivityIndicator];
        if (completedBlock) {
            NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
            completedBlock(nil, error, SDImageCacheTypeNone, url);
        }
    });
}
}

3、知識點簡析
1)為什么要判斷以下代碼,為什么判斷了兩次?

 if (!sself) {
        return;
    }

假如對象是UITableViewCell上的一個UIImageView,就會隨著頁面滑動Cell消失的時候被釋放掉。而此時有可能圖片剛加載出來。判斷是為了防止對象釋放所帶來的異常。而之后又判斷了一次,是防止在切換線程的過程中對象被釋放的情況。畢竟切換線程也是需要時間的。

2) dispatch_main_async_safe(block)。從上述代碼我們可以看出,所有更新UI的操作都是在dispatch_main_async_safe()中完成的。那為什么我們不直接切換到主線程去刷新UI呢?

#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
    block();\
} else {\
    dispatch_async(dispatch_get_main_queue(), block);\
}
#endif

點進去發現,這個方法的含義是先比較當前線程是否為主線程,是則直接執行block(這時候就不用切換線程了,因為切換線程也是要耗費時間的),不是再切換到主線程執行block。

3)為什么用了__weak之后又用了__strong呢?

    __weak __typeof(self)wself = self;
    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;

我們都知道,用__weak是防止循環引用。而之后又用了__strong,是避免weak修飾的變量隨機被釋放而導致異常。那block 里 strong self 后,block 不是也會持有 self 嗎?而 self 又持有 block ,那不是又循環引用了? 當然不會咯,因為此時的sself是一個新的引用(變量/對象)了,已經不是self了。

二、 loadImageWithURL--圖片加載器

在URL正確的情況下,先從緩存中找,如果沒找到就去下載。 并將下載好或從緩存中查找到的圖片返回
1、程序主流程


image.png

2、源碼分析

- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                 options:(SDWebImageOptions)options
                                progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                               completed:(nullable SDInternalCompletionBlock)completedBlock {
//如果有completedBlock  就在控制臺打印如下信息
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

//很多人都會把URL當成字符串來傳,那如果真接收到字符串了,我們就轉換一下~
if ([url isKindOfClass:NSString.class]) {
    url = [NSURL URLWithString:(NSString *)url];
}

//有時候也會發送NSNull這樣的情況,為了更嚴謹,我們再判斷一下
if (![url isKindOfClass:NSURL.class]) {
    url = nil;
}

//初始化一個可以控制加載圖片方法的一個操作,這個操作里面既能控制緩存,又能控制下載。而且在方法結束的時候返回給外界
//__block,即在block內可修改。
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;

//判斷這個URL是否是個錯誤的URL
BOOL isFailedUrl = NO;
if (url) {
    @synchronized (self.failedURLs) {
        isFailedUrl = [self.failedURLs containsObject:url];
    }
}

//如果URL不合格就直接返回了,也沒有必要去緩存里查找啦
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;
}

//如果合格,加到runningOperations里面
@synchronized (self.runningOperations) {
    [self.runningOperations addObject:operation];
}
NSString *key = [self cacheKeyForURL:url];

//URL合格,先去緩存中找,并返回查詢結果。 此緩存為二級緩存,即先去內存中找,如果找不到,去磁盤里找,然后再返回結果。
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
    if (operation.isCancelled) {
        [self safelyRemoveOperationFromRunning:operation];
        return;
    }
    //此后根據查詢結果來判斷是否需要下載。以下是需要下載的條件
    //  如果緩存中沒找到圖片
    //||用戶設置了更新緩存 && 用戶沒有響應imageManager:shouldDownloadImageForURL:(沒有響應就不會出現返回NO的情況,就可以下載了)
    //||用戶執行了imageManager:shouldDownloadImageForURL:,并返回yes(既這個網址需要被下載)
    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];

                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];
                    }
                }
                BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                //如果沒有downloadedImage,也不用存儲了,而且options & SDWebImageRefreshCached && cachedImage  這種情況下已經callback過了,也不用進行callCompletion操作,所以什么都不用做
                if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                
                } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                    //以下這種情況會存儲被用戶轉換過的圖片
                    //   如果有downloadedImage
                    // &&(下載的圖片不是GIF || 用戶設置了即使是GIF也可以transform)
                    // &&用戶要對圖片做一些轉換(即響應了這個代理方法“(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];
                            // 如果圖片被轉換過,imageData就存儲一個nil
                            [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 {
                    // 存儲正常圖片。判斷一下finished是因為 如果用戶設置了options & SDWebImageProgressiveDownload,那在下載過程中每接收到一點數據didReceiveData,就會執行callCompletion,以便用戶實現漸進效果,但此時圖片并沒有下載完成,所以不需要存儲。
                    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) {
                [self safelyRemoveOperationFromRunning:strongOperation];
            }
        }];
        //給operation的cancelBlock賦值,這個block主要用于終止下載操作。當外界調用operation的cancell方法時,這個block會被執行,
        //同時這個operation里面的cacheOperation也會被cancell。 所以它既能控制下載也能控制緩存,返回給外界以方便使用。
        operation.cancelBlock = ^{
            [self.imageDownloader cancel:subOperationToken];
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self safelyRemoveOperationFromRunning:strongOperation];
        };
    //如果從緩存中查詢到圖片了,就callCompletion查詢到的圖片
    } 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];
    //如果緩存中沒有查詢到圖片,并且用戶不允許下載,即執行過[self.delegate imageManager:self shouldDownloadImageForURL:url]這個方法后返回NO。
    } 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;
}

3、知識點解析
1)斷言(NSAssert)

#define NSAssert(condition,desc)

如果condition不成立,則程序崩潰。并在控制臺打印desc。

NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

如源碼中的代碼,如果沒有completedBlock,程序就會崩潰,并會在控制臺打印If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead。從源代碼中我們發現,確實在往后的代碼中沒有判斷是否有completedBlock,都是直接調用completedBlock的。

2)@synchronized (self.runningOperations) {...}

@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}

這是一個互斥鎖,防止多條線程同一時間對self.runningOperations進行修改。也就是說,當一個線程搶占了這個任務時,這個鎖就會排斥其他線程,起到了線程保護的作用。保證了self.runningOperations的準確性。

三、queryCacheOperationForKey --存儲器

1、程序流程


image.png

2、源碼分析

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
//如果key是nil,就直接doneBlock,返回
if (!key) {
    if (doneBlock) {
        doneBlock(nil, nil, SDImageCacheTypeNone);
    }
    return nil;
}

//否則,先從內存中找
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
    // 如果找到了,就可以doneBlock,返回了
    NSData *diskData = nil;
    if ([image isGIF]) {
        diskData = [self diskImageDataBySearchingAllPathsForKey:key];
    }
    if (doneBlock) {
        doneBlock(image, diskData, SDImageCacheTypeMemory);
    }
    return nil;
}

//如果沒有找到,就去磁盤中找
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
    if (operation.isCancelled) {
        // do not call the completion if cancelled
        return;
    }

    @autoreleasepool {
        NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        UIImage *diskImage = [self diskImageForKey:key];
        //如果應該在內存中存儲的話就在內存存儲一份
        if (diskImage && self.config.shouldCacheImagesInMemory) {
            NSUInteger cost = SDCacheCostForImage(diskImage);
            [self.memCache setObject:diskImage forKey:key cost:cost];
        }

        if (doneBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
            });
        }
    }
});
return operation;
}

3、知識點簡析
1)NSCache
大家可以找一下相關資料,在這里就不做多的分析了,(__) ……
2)磁盤里面的沙盒

image.png

當我們使用HSHomeDirectory()就可以訪問到我們APP所對應的沙盒了。在沙盒里,系統默認為我們創建三個文件夾,分別是Documents,Library和tmp。當然我們也可以在沙盒路徑下自己新建文件夾。
Documents主要放一些不占空間,又比較重要的文件。如果應用需要備份,系統也是備份Documents下的文件。
Library主要放一些大文件。又多又占地方。比如微信的聊天記錄就是存放在這個文件夾下面的,SDWebImage也是把圖片存放在這個文件夾下面的。
tmp主要放一些臨時文件。
我們把Library文件夾打開
image.png

偏好設置[NSUserDefaults standardUserDefaults]就是存放在Library目錄下的,同時系統還自動為我們生成了一個文件夾叫Caches,也可以在這個目錄下自己生成目錄。
3)為什么要使用自動釋放池?
這里添加的自動釋放池與以下代碼有異曲同工之處

for (int i = 0; i < 10000; i++) {
    @autoreleasepool {
        NSString *str = [NSString stringWithFormat:@"zyy"];
    }
 }

去磁盤中查詢圖片也是也是重復執行的任務。為了防止對象不斷增多,每查詢一次就將所用到的對象釋放,可以降低內存峰值,防止內存持續增加,再突然降低。

四、downloadImageWithURL--下載器

1、程序主流程


image.png

2、源碼分析

 - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                               options:(SDWebImageDownloaderOptions)options
                                              progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                             completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
__weak SDWebImageDownloader *wself = self;

     // 這個方法主要調用了addProgressCallback這個方法,既創建一個下載任務,返回這個任務的標識

     return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback: 
     {我是初始化下載任務,并將這個任務返回的block}];
}

以下是addProgressCallback方法源碼

- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
                                               forURL:(nullable NSURL *)url
                                       createCallback:(SDWebImageDownloaderOperation *(^)(void))createCallback {
// The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
if (url == nil) {
    if (completedBlock != nil) {
        completedBlock(nil, nil, nil, NO);
    }
    return nil;
}

__block SDWebImageDownloadToken *token = nil;

dispatch_barrier_sync(self.barrierQueue, ^{
    //先看看與這個URL對應的operation是否存在,防止對同一個網址下載多次
    SDWebImageDownloaderOperation *operation = self.URLOperations[url];
    if (!operation) {
   //當operation 不存在的時候才執行這個block,即創建下載任務,并對其進行初始化
   //注?。。。哼@個時候才執行從downloadImageWithURL方法中傳過來的那個代碼塊兒~!這個時候可以跳到下一塊兒源碼,看看是如何創建并初始化的
        operation = createCallback();
        self.URLOperations[url] = operation;

        __weak SDWebImageDownloaderOperation *woperation = operation;
        //當這個下載任務完成的時候,從URLOperations中移除
        operation.completionBlock = ^{
            dispatch_barrier_sync(self.barrierQueue, ^{
                SDWebImageDownloaderOperation *soperation = woperation;
                if (!soperation) return;
                if (self.URLOperations[url] == soperation) {
                    [self.URLOperations removeObjectForKey:url];
                };
            });
        };
    }
    id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];

    //為這個下載任務配一個標識,方便外界隨時cancell
    token = [SDWebImageDownloadToken new];
    token.url = url;
    token.downloadOperationCancelToken = downloadOperationCancelToken;
});

return token;
}

那個創建下載任務的代碼塊兒

^SDWebImageDownloaderOperation *{
    //這個block主要初始化了一個下載任務,并將它返回
    __strong __typeof (wself) sself = wself;
    NSTimeInterval timeoutInterval = sself.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
    NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
                                                                cachePolicy:cachePolicy
                                                            timeoutInterval:timeoutInterval];
    
    request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
    request.HTTPShouldUsePipelining = YES;
    if (sself.headersFilter) {
        request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
    }
    else {
        request.allHTTPHeaderFields = sself.HTTPHeaders;
    }
    
    //自定義了自己的operation,專程用來下載的。  顯然這個時候系統的NSBlockOperation 和 NSInvocationOperation 已經滿足不了需求了,需要自定義了
    //以下都是給這個下載操作初始化的代碼,就不細說咯
    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];
    }
    
    if (options & SDWebImageDownloaderHighPriority) {
        operation.queuePriority = NSOperationQueuePriorityHigh;
    } else if (options & SDWebImageDownloaderLowPriority) {
        operation.queuePriority = NSOperationQueuePriorityLow;
    }
    //初始化以后并將這個操作添加到隊列里面
    [sself.downloadQueue addOperation:operation];
    //設置執行順序。如果是后進先出,那就把線程添加到最先執行那個線程的后面
    if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
        [sself.lastAddedOperation addDependency:operation];
        sself.lastAddedOperation = operation;
    }

    return operation;

3、知識點簡析
1)下載任務SDWebImageDownloaderOperation類是怎么下載的?
具體代碼就不分析了,來簡單的淺析一下(偷個懶O(∩_∩)O~)。它是對NSOperation的一個封裝(想學習自定義NSOperation的童鞋,SDWebImage真的是個很好的例子)。 簡單來講,就是我們把網絡下載的URL啊,網絡證書啊,options啊....還有它需要執行的progressBlock和completeBlock給它,然后它去請求網絡,把請求回來的數據先解讀成一張圖片,并通過回調progressBlock和completeBlock把圖片返還給我們。
2)dispatch_barrier_syncdispatch_barrier_async

     dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t):

這個方法重點是你傳入的 queue,當你傳入的 queue 是通過 DISPATCH_QUEUE_CONCURRENT參數自己創建的 queue 時,這個方法會阻塞這個 queue(注意是阻塞 queue ,而不是阻塞當前線程),一直等到這個 queue 中排在它前面的任務都執行完成后才會開始執行自己,自己執行完畢后,再會取消阻塞,使這個 queue 中排在它后面的任務繼續執行。
如果你傳入的是其他的 queue, 那么它就和dispatch_async 一樣了。

  dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t):

這個方法的使用和上一個一樣,傳入 自定義的并發隊列DISPATCH_QUEUE_CONCURRENT,它和上一個方法一樣的阻塞 queue,不同的是 這個方法還會 阻塞當前線程。
如果你傳入的是其他的 queue, 那么它就和 dispatch_sync一樣了。

總結:當我們使用SDWebImage給UI賦值時,它會先從內存中讀取圖片,讀取不到再從磁盤中讀取,都沒有讀取成功就去下載圖片,下載成功存入緩存,并給UI賦值。其中下載器、緩存器、加載器和UI賦值器分工明確,條理清晰。 牽扯的知識面也很廣,有下載類NSURLSession、緩存類NSCache、對磁盤的操作NSFileManager,以及多線程 dispatch和NSOperation等。也是學習這些模塊兒的一個很好的例子呀!
好啦。以上是我對SDWebImage源碼流程上的分析,有不對的地方也盡管指出來~~我們共同學習,嘿嘿

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容