SDWebImage源碼初探(一)

最近項目進度緩慢了下來,決定看看各種源碼來漲點知識。就先從SDWebImage開始吧!

在項目中用的最多的方法應該是UIImageView+WebCache與UIButton+WebCache里面的sd_setImageWithURL:這個系列的方法了。這里從UIImageView+WebCache開始看起。

其中該系列所有方法都基于:


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

參數很簡單明了,url是需要加載的圖片url;placeholder是在url指向的圖片加載完成之前暫時使用的圖片;options是個枚舉,用來控制加載圖片時的一些設置(具體百度即可,大把);progressBlock則可以通過返回的receivedSize與expectedSize來花式展現下載進度;completedBlock則是下載完成后的Block;

下面來看作者的具體實現:


[self sd_cancelCurrentImageLoad];

按照意思來看是取消當前所有的圖片加載,進入方法內部看:


- (void)sd_cancelImageLoadOperationWithKey:(NSString*)key {

// Cancel in progress downloader from queue

NSMutableDictionary*operationDictionary = [selfoperationDictionary];

idoperations = [operationDictionaryobjectForKey:key];

if(operations) {

if([operationsisKindOfClass:[NSArrayclass]]) {

for(id operationinoperations) {

if(operation) {

[operationcancel];

}

}

}elseif([operationsconformsToProtocol:@protocol(SDWebImageOperation)]){

[(id) operationscancel];

}

[operationDictionaryremoveObjectForKey:key];

}

}

首先取出一個operationDictionary,在取出這個字典的過程中,作者在分類中使用了objc_setAssociatedObject與objc_getAssociatedObject,來給分類添加屬性。

接著通過傳過來的key(@"UIImageViewImageLoad")來獲取ImageView的加載隊列。

最后,花式cancle掉這個隊列中的任務。


[operation cancel];

[operation DictionaryremoveObjectForKey:key];

回到主方法,第二行又通過objc_setAssociatedObject方法將url關聯到分類中,接著通過位與運算判斷下option是否為SDWebImageDelayPlaceholder,來設置默認的占位圖片。其中dispatch_main_async_safe這個宏很好用,避免了在主線程中造成死鎖的情況。

然后是判斷一下是否需要轉動菊花:


if([selfshowActivityIndicatorView]) {

[selfaddActivityIndicator];

}

接下來是這個方法的核心部分,調用了SDWebImageManager中的


- (id)downloadImageWithURL:(NSURL*)url

options:(SDWebImageOptions)options

progress:(SDWebImageDownloaderProgressBlock)progressBlock

completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;

由此方法完成圖片的下載。

我們可以跳到SDWebImageManager.h中查看一下作者對于該方法的描述:

翻譯過來的意思大概是:如果URL指定的圖片不在緩存中就下載該圖片,否則就返回緩存的版本。

回到這個方法的實現,前幾行是判斷URL的類型是否正確。


BOOLisFailedUrl =NO;

@synchronized(self.failedURLs) {

isFailedUrl = [self.failedURLscontainsObject:url];

}

這幾句來獲取傳入的URL是否為之前下載失敗過的URL,用一個BOOL值來記錄下來。

如果URL不為空或者未設置options為SDWebImageRetryFailed項、且URL在黑名單之中,就會直接返回掉。


@synchronized(self.runningOperations) {

[self.runningOperationsaddObject:operation];

}

這段代碼是先給運行中的下載隊列加鎖,避免多個線程同時對數組進行操作,將一個SDWebImageCombinedOperation對象加入到下載隊列中。


NSString*key = [selfcacheKeyForURL:url];

operation.cacheOperation= [self.imageCache queryDiskCacheForKey:key done:^(UIImage*image,SDImageCacheTypecacheType) {

if(operation.isCancelled) {

@synchronized(self.runningOperations) {

[self.runningOperationsremoveObject:operation];

}

return;

}

將圖片的URL當做key值,再調用 queryDiskCacheForKey:done:來獲取緩存中的圖片。
我們來看看這個方法的內部實現:

- (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];
//這里封裝了NSChace的objectForKey方法,直接從內存緩存中獲取圖片對象
    if (image) {
        doneBlock(image, SDImageCacheTypeMemory);
        return nil;
    }
//如果內存緩存中獲取到則直接返回

    NSOperation *operation = [NSOperation new];
    dispatch_async(self.ioQueue, ^{
        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(), ^{
                doneBlock(diskImage, SDImageCacheTypeDisk);
//在主線程中回調Block
            });
        }
    });

    return operation;
}

緩存這里獲取完成之后,來看下面的代碼,有點長,我們分解開來一部分一部分閱讀,先看這個判斷:

if ((!image || options & SDWebImageRefreshCached)
//圖片未從緩存中獲取,或者是 option設置需要刷新緩存
&& (![self.delegate respondsToSelector:
@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]))
/*這部分條件中,如果imageManager:shouldDownloadImageForURL:方法未實現、或是實現了并且返回YES。可以從方法的名字中來理解,代理方法返回的BOOL為是否應該下載URL對應的圖片。
*/

總而言之就是判斷各種條件之下,圖片是否應該被下載,讓我們進入方法內部。

if (image && options & SDWebImageRefreshCached) {
                dispatch_main_sync_safe(^{
                    // If image was found in the cache bug 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.
//翻譯一下,如果圖片在緩存中被找到但是options設置了SDWebImageRefreshCached(刷新緩存),通知這個緩存圖片,并且試圖從新下載這個圖片,讓服務端有機會刷新這個緩存。
                    completedBlock(image, nil, cacheType, YES, url);
                });
            }
SDWebImageDownloaderOptions downloaderOptions = 0;
//初始化downloaderOptions
            if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
            //如果options為低優先級,則設置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) {
                // 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;
                //翻譯一哈,兩行代碼的意思是,如果圖片存在緩存并且需要刷新緩存,則強制取消掉SDWebImageDownloaderProgressiveDownload模式(漸進下載),
然后忽略從緩存中讀取的圖片。
            }

接下來使用SDWebImageDownloader來執行一個下載任務

id <SDWebImageOperation> subOperation = 
[self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:
^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) 

來看下載completedBlock中的第一部分處理

if (weakOperation.isCancelled) {
                  /*這里什么都沒做操作,源代碼中提及了#699號更新,于是我去看了下,大概意思是說:
當weakOperation取消的時候不要試圖去調用completion block,dispatch_main_sync_safe()也無法保證這個block被終止的時候沒有其他的代碼在運行,所以其他代碼運行時可能會被截斷。
比如說,如果取消weakOperation后再調用completion block,那么在隨后的一個TableViewCell中加載Image的completion block將會和這個completion block產生競爭關系。說的通俗一點就是,先調用的completion block里面的數據可能會被第二個completion block的數據覆蓋掉。
*/
                }
                else if (error) {
                    dispatch_main_sync_safe(^{
                        if (!weakOperation.isCancelled) {
                            completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
                        }
//有錯誤信息,完成回調。
                    });

                    if (error.code != NSURLErrorNotConnectedToInternet && error.code != NSURLErrorCancelled && error.code != NSURLErrorTimedOut) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
//將發送錯誤的URL添加到黑名單里面
                        }
                    }
                }
else {
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                    if (options & SDWebImageRefreshCached && image && !downloadedImage) {
//options設置為SDWebImageRefreshCached選項,在緩存中又找到了image且沒有下載成功
                        // Image refresh hit the NSURLCache cache, do not call the completion block
                      //圖片刷新時遇到了具有緩存的情況,不調用 completion block
                    }
                    else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage))
                    && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                    //圖片下載成功且圖片設置為SDWebImageTransformAnimatedImage并且實現了imageManager:transformDownloadedImage:withURL:方法            
          dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
                            //調用delegate方法完成圖片的變形
                            if (transformedImage && finished) {
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:data forKey:key toDisk:cacheOnDisk];
                              //將變形后的圖片緩存起來
                            }

                            dispatch_main_sync_safe(^{
                                if (!weakOperation.isCancelled) {
                                    completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
                              //在主線程中回調completedBlock
                                }
                            });
                        });
                    }
                    else {
                        if (downloadedImage && finished) {
                            [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
                          //如果沒設置圖片變形,并且下載完成,則直接緩存圖片
                        }

                        dispatch_main_sync_safe(^{
                            if (!weakOperation.isCancelled) {
                                completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
                            //在主線程中完成回調
                            }
                        });
                    }
                }
if (finished) {
                    @synchronized (self.runningOperations) {
                        [self.runningOperations removeObject:operation];
                   //從下載隊列移除
                    }
                }
            }];
operation.cancelBlock = ^{
                [subOperation cancel];
                @synchronized (self.runningOperations) {
                    [self.runningOperations removeObject:weakOperation];
                
                }
            };
             //設置operation取消之后的一些操作
else if (image) {
        else if (image) {
            dispatch_main_sync_safe(^{
                if (!weakOperation.isCancelled) {
                    completedBlock(image, nil, cacheType, YES, url);
                }
                //在緩存中找到圖片并且設置了不能下載的選項,完成回調
            });
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }
        }
        else {
         //在緩存中沒有找到圖片,并且設置不能下載的選項
      
            dispatch_main_sync_safe(^{
                if (!weakOperation.isCancelled) {
                    completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
                }
             //完成回調
            });
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }
        }
    }];

    return operation;

這么一大段的方法按照功能排序來看,分解為首先創建下載operation,再讀取系統的內存緩存與磁盤緩存,接著判斷是否需要下載來進行下載操作,最后對下載的圖片進行處理。

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

推薦閱讀更多精彩內容