首頁 > 軟體

原始碼解析ios開發SDWebImage方法

2022-08-25 14:01:33

引言

在著手寫第二篇的時候,發現這個SDWebimage確實吧NSOperation用的太美了。確實可能幫你理解NSOperationNSOperationQueue,當然還有Block的佇列。還有一個GCD

各位看官在看的時候可以著重的看看他的operatinQueue的佇列。看看是怎麼新增到佇列的以及是怎麼移除佇列。在後面的文章就會提到他是怎麼執行的。 還要注重看的就是以前用的NSURLConnection而現在用的NSURLSession下來了

最近在面試,發現有人問這個元件,所有讀一讀,應付一下面試吧!!

原始碼解析

廢話不多說看原始碼。

  • 1:在元件中提供了很多類似這樣的方法
- (void)sd_setImageWithURL:(nullable NSURL *)url;
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder;
  • 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
  • 3:下面具體閱讀程式碼 第一行執行的程式碼
//********1: 所有的設定控制元件圖片都是經過該方法*******
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
//********2: 取消當前控制元件正在operations的佇列*******
[self sd_cancelImageLoadOperationWithKey:validOperationKey];

解析:

1.在第一行建立了validOperationKey的operationKey,是以當前擴充套件的類名命名。

2.執行sd_cancelImageLoadOperationWithKey方法

- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
    // Cancel in progress downloader from queue
    /**
     * 在該物件中,用runtime手動的新增一個字典屬性。
     ### 說一下這裡的operationDictionary
     ### 該字典的value是下載的操作,然而key是對檢視和操作來做的標識字串
    */
    SDOperationsDictionary *operationDictionary = [self operationDictionary];
    /*
     * 在生成字典中的都是新的,所有都沒有資料
     */
    id operations = operationDictionary[key];
    if (operations) {
        if ([operations isKindOfClass:[NSArray class]]) {
            for (id <SDWebImageOperation> operation in operations) {
                if (operation) {
                    [operation cancel];
                }
            }
        } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
            [(id<SDWebImageOperation>) operations cancel];
        }
        [operationDictionary removeObjectForKey:key];
    }
}

字典操作

在看一下去字典操作[self operationDictionary]

- (SDOperationsDictionary *)operationDictionary {
    /**
     ### 這裡有一個疑問?
     這樣建立出來的字典每一次都是新的
    雖然用了 static char loadOperationKey,但是沒一次建立的地址都是新的,並且該字典還是空的。
     建立完成之後都直接返回了。不知道每次建立的都是新的並且還是空的字典,這樣的開銷會很大的??!!!,也希望各位看官給予解答啊???
     */
    SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
    // 建立成功返回該字典,直接把該字典當做屬性返回
    if (operations) {
        return operations;
    }
    operations = [NSMutableDictionary dictionary];
    objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return operations;
}

看到這裡我們在返回繼續看 這個函數的程式碼有點長,耐心看完啊。。。

- (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 {
    //********1: 所有的設定控制元件圖片都是經過該方法*******
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    /**2: 取消當前控制元件正在operations的佇列
     * 至於為什麼在該控制元件下載前都要清空該控制元件對應的所有的下載佇列?
     * 可能是,如果要給控制元件賦值新的圖片的話,之前所有的操作都和當前的操作無關,所有就取消吧
    *******/
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    /**3:設定佔點陣圖片*/
    /**這裡的意思就是options引數不是SDWebImageDelayPlaceholder,就執行以下操作   */
    if (!(options & SDWebImageDelayPlaceholder)) {
        /**
         * 注意這的裡宏定義
         * #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方法主執行緒中去執行。
         應為在系統裡,只能有一個執行緒去執行UI的更新,那就是主執行緒。
         如果能在其他執行緒更新,那這世界就亂了,像瞭解
         更仔細的問題可以關注www.osjoin.com檢視是為什麼!
         */
        dispatch_main_async_safe(^{
            /**設定佔點陣圖placeholder*/
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });
    }
    /**4:菊花提示*/
    if (url) {
        // check if activityView is enabled or not
        if ([self sd_showActivityIndicatorView]) {
            [self sd_addActivityIndicator];
        }
        __weak __typeof(self)wself = self;
        /**5:下載圖片
         * 進入下載圖片最重要的函數也是核心的函數了
         這個函數關聯的函數較多,先簡單過一下。
         */
        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];
            if (!sself) {
                return;
            }
            dispatch_main_async_safe(^{
                if (!sself) {
                    return;
                }
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                    completedBlock(image, error, cacheType, url);
                    return;
                } else if (image) {
                    [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                    [sself sd_setNeedsLayout];
                } else {
                    if ((options & SDWebImageDelayPlaceholder)) {
                        [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                        [sself sd_setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        /**將行的下操作放到uiview的下載佇列中的自定義的字典中去*/
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } 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);
            }
        });
    }
}

看一下呼叫下載函數前的範例化過程

這個loadImageWithURL函數在SDWebImageManager,並且是用單列呼叫,

+ (nonnull instancetype)sharedManager {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}
- (nonnull instancetype)init {
    /**
     ###此處有其他重要設定,下一篇文章解讀
     ###此處有其他重要設定,下一篇文章解讀
     ###此處有其他重要設定,下一篇文章解讀
     */
    SDImageCache *cache = [SDImageCache sharedImageCache];
    SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
    return [self initWithCache:cache downloader:downloader];
}
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader {
    if ((self = [super init])) {
        _imageCache = cache;
        _imageDownloader = downloader;
        /**這裡的failedURLS是NSSet也就沒重複的屬性*/
        _failedURLs = [NSMutableSet new];
        _runningOperations = [NSMutableArray new];
    }
    return self;
}

上面都是初始化的操作,然後看下面的函數

- (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
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
    // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
    // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
    /**
     這裡防止使用者輸入的型別錯誤,轉換一下
     */
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }
    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    /*! 如果轉換失敗,url為nil 返回 */
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }
    /**5.1:搞一個新的下載佇列*/
    /*! 這裡就又__block和__weak的用法區別
     這裡簡單說說一下
     1:__block,在block函數裡可以修改和閱讀
     2:__weak可以避免迴圈參照,在給他設定新資料的時候,設定方法既不保留新值,也不釋放舊值
     */
    /*! 說一下SDWebImageCombinedOperation
     他擁有了一個快取佇列和一個能取消執行的block,並且還遵守了SDWebImageOperation一個協定
     */
    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;
    /**5.2判斷URL是否是下載失敗的url*/
    BOOL isFailedUrl = NO;
    if (url) {
        /*! 建立一個互斥鎖防止有其他執行緒同時修改該物件 */
        @synchronized (self.failedURLs) {
            isFailedUrl = [self.failedURLs containsObject:url];
        }
    }
    /**
     * 5.3url不存在或者下載失敗的url 則不在繼續下載佇列
     * 返回一個completedBlock
     */
    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;
    }
    /**5.4把url新增在下載佇列
     把operation加入到self.runningOperations的陣列裡面,
     並建立一個互斥執行緒鎖來保護這個操作
     */
    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
    /*! 獲取image的url對應的key */
    NSString *key = [self cacheKeyForURL:url];
    /**5.5快速查詢***快取*****/
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        /**
         * 這裡的狀態改變,在sd_cancelImageLoadOperationWithKey方法中呼叫代理函數時會改變該狀態cancel
         * 如果該佇列是取消狀態,直接從下載佇列中移除,此處有一個安全鎖
         */
        if (operation.isCancelled) {
            [self safelyRemoveOperationFromRunning:operation];
            return;
        }
        /**下載完成之後執行圖片轉換處理和快取操作**/
        //條件1:在快取中沒有找到圖片或者options選項裡面包含了SDWebImageRefreshCached(這兩項都需要進行請求網路圖片的)
        //條件2:代理允許下載,SDWebImageManagerDelegate的delegate不能響應imageManager:shouldDownloadImageForURL:方法或者能響應方法且方法返回值為YES.也就是沒有實現這個方法就是允許的,如果實現了的話,返回YES才是允許
        if ((!cachedImage || options & SDWebImageRefreshCached) &&
            (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            //如果在快取中找到了image且options選項包含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.
                /** 如果在快取中找到了image但是設定了SDWebImageRefreshCached選項,傳遞快取的image,同時嘗試重新下載它來讓NSURLCache有機會接收伺服器端的更新
                dispatch_main_async_safe(^{
                    if (operation && !operation.isCancelled && completionBlock) {
                        completionBlock(image, data, error, cacheType, finished, url);
                    }
                });
                 */
                [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }
            // 如果沒有在快取中找到image 或者設定了需要請求伺服器重新整理的選項,則仍需要下載
            // 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
                // 如果image已經被快取但是設定了需要請求伺服器重新整理的選項,強制關閉漸進式選項
                downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
                // ignore image read from NSURLCache if image if cached but force refreshing
                // 如果image已經被快取但是設定了需要請求伺服器重新整理的選項,忽略從NSURLCache讀取的image
                downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
            }
            /**如果在快取和硬碟上都沒查到url對應的圖片
             ***則進行圖片下載
             */
            /*! 進入下載操作就是2.2中的操作了*/
            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) {
                    // 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
                    //如果我們呼叫completedBlock,這個block會和另外一個completedBlock爭奪一個物件,因此這個block被呼叫後會覆蓋新的資料
                } 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) {
                        //將失敗的url新增到failedURLS的set中去
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {
                    //如果有重試狀態,將url從失敗列表中移除
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
                    //options包含了SDWebImageRefreshCached選項,且快取中找到了image且沒有下載成功
                    if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                        // Image refresh hit the NSURLCache cache, do not call the completion block
                    } else if (
                               //圖片下載成功並且 設定了需要變形Image的選項且變形的代理方法已經實現
                               downloadedImage &&
                               (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) &&[self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]
                               ) {
                        /**
                         * dispatch_get_global_queue建立的一個//全域性佇列非同步佇列執行
                         */
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            //呼叫代理方法完成圖片transform
                            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
                                //對已經transform的圖片進行快取
                                [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 {
                        //如果沒有圖片transform的需求並且圖片下載完成且圖片存在就直接快取
                        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 = ^{
                [self.imageDownloader cancel:subOperationToken];
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                [self safelyRemoveOperationFromRunning:strongOperation];
            };
            //在快取中找到圖片(代理不允許下載 或者沒有設定SDWebImageRefreshCached選項  滿足至少一項)
        } 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;
}

這個函數就進入了SDWebimage快取的策略了。

先說一下他的這一個策略快取。

*1:大家都是SDWebiamge都是先從快取上查詢,如果有就直接顯示

*2:如果不存在就在沙盒中查詢 

  • *2.1如果存在,則把沙盒中的圖片新增到imageCache中,取出顯示 
  • *2.2 如果不存在在顯示佔點陣圖,根據URL在operationCache是否存在下載操作 

*2.2.1 如果存在,說明該圖片正在下載。

*2.2.2如果不存在,建立圖片下載操作,放到operationCache中

  • * 2.3 下載完成,將當前操作佇列從operationCache中移除。並將下載的圖片的新增在imageCache中。顯示

先慢慢體會一下。。。(停留30秒)

開始進入查詢函數

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    /**從快取中查詢圖片開始*/
    /*! 檢查key是否為空(URL) */
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
    // 先檢查快取--記憶體上的資料
    // First check the in-memory cache...
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        /**從記憶體在檢查到有圖片**/
        NSData *diskData = nil;
        if ([image isGIF]) {
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
        /**如果有直接返回在view上顯示*/
        if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        return nil;
    }
    /**如果記憶體上沒有資料,則在硬碟上查詢,如果找到了該圖片,就放到快取上用doneBlock完成回撥**/
    /*! 這一塊建立了非同步佇列
     這裡的self.ioQueue是這樣定義的
     ****@property (strong, nonatomic, nullable) dispatch_queue_t ioQueue;
     ****這裡開始了序列的佇列去處理硬碟上的快取
     */
    NSOperation *operation = [NSOperation new];
    dispatch_async(self.ioQueue, ^{
        /**如果是取消的 就直接返回*/
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
        }
        /*! 開了手動釋放池 */
        @autoreleasepool {
            /**從磁碟中讀取圖片*/
            /*! 根據url去硬碟上查詢資料 */
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                /**如果在硬碟中讀取到圖片,則把沙盒中的圖片放到Cache中*/
                //self.memCache是NSCache建立的一個物件
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }
            if (doneBlock) {
                /*! 在主執行緒放回資料 */
                dispatch_async(dispatch_get_main_queue(), ^{
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
        }
    });
    return operation;
}

快速查詢快取的方法回撥

看完該函數以後在回到上面的看這個快速查詢快取的方法回撥

operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
        if (operation.isCancelled) {
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }
            return;
        }
        if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
//如果在快取中找到了image且options選項包含SDWebImageRefreshCached,先在主執行緒完成一次回撥,使用的是快取中找的圖片
            if (image && options & SDWebImageRefreshCached) {
                dispatch_main_sync_safe(^{
                // 如果在快取中找到了image但是設定了SDWebImageRefreshCached選項,傳遞快取的image,同時嘗試重新下載它來讓NSURLCache有機會接收伺服器端的更新
                    completedBlock(image, nil, cacheType, YES, url);
                });
            }
            // 如果沒有在快取中找到image 或者設定了需要請求伺服器重新整理的選項,則仍需要下載
            SDWebImageDownloaderOptions downloaderOptions = 0;
            //開始各種options的判斷
            if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
            if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
            if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
            if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
            if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
            if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
            if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
            if (image && options & SDWebImageRefreshCached) {
            // 如果image已經被快取但是設定了需要請求伺服器重新整理的選項,強制關閉漸進式選項
                downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
               // 如果image已經被快取但是設定了需要請求伺服器重新整理的選項,忽略從NSURLCache讀取的image
                downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
            }
            //建立下載操作,先使用self.imageDownloader下載
            id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if (!strongOperation || strongOperation.isCancelled) {
                    // Do nothing if the operation was cancelled
                    //如果操作取消了,不做任何事情
                    // 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
                //如果我們呼叫completedBlock,這個block會和另外一個completedBlock爭奪一個物件,因此這個block被呼叫後會覆蓋新的資料
                }
                else if (error) {
                    //進行完成回撥
                    dispatch_main_sync_safe(^{
                        if (strongOperation && !strongOperation.isCancelled) {
                            completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
                        }
                    });
                  //將url新增到失敗列表裡面
                    if (   error.code != NSURLErrorNotConnectedToInternet
                        && error.code != NSURLErrorCancelled
                        && error.code != NSURLErrorTimedOut
                        && error.code != NSURLErrorInternationalRoamingOff
                        && error.code != NSURLErrorDataNotAllowed
                        && error.code != NSURLErrorCannotFindHost
                        && error.code != NSURLErrorCannotConnectToHost) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {
                    //如果設定了下載失敗重試,將url從失敗列表中去掉
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
        //options包含了SDWebImageRefreshCached選項,且快取中找到了image且沒有下載成功
                    if (options & SDWebImageRefreshCached && image && !downloadedImage) {
                        // Image refresh hit the NSURLCache cache, do not call the completion block
 // 圖片重新整理遇到了NSSURLCache中有快取的狀況,不呼叫完成回撥。
                }
  //圖片下載成功並且 設定了需要變形Image的選項且變形的代理方法已經實現
                    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), ^{
                            //呼叫代理方法完成圖片transform
                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
                            if (transformedImage && finished) {
                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                //對已經transform的圖片進行快取
                                [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
                            }
                            //主執行緒執行完成回撥
                            dispatch_main_sync_safe(^{
                                if (strongOperation && !strongOperation.isCancelled) {
                                    completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
                                }
                            });
                        });
                    }
//如果沒有圖片transform的需求並且圖片下載完成且圖片存在就直接快取
                    else {
                        if (downloadedImage && finished) {
                            [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
                        }
                   //主執行緒完成回撥 
                        dispatch_main_sync_safe(^{
                            if (strongOperation && !strongOperation.isCancelled) {
                                completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
                            }
                        });
                    }
                }
                if (finished) {
       // 從正在進行的操作列表中移除這組合操作
                    @synchronized (self.runningOperations) {
                        if (strongOperation) {
                            [self.runningOperations removeObject:strongOperation];
                        }
                    }
                }
            }];
          //設定組合操作取消得得回撥
            operation.cancelBlock = ^{
                [subOperation cancel];
                @synchronized (self.runningOperations) {
                    __strong __typeof(weakOperation) strongOperation = weakOperation;
                    if (strongOperation) {
                        [self.runningOperations removeObject:strongOperation];
                    }
                }
            };
        }
//處理其他情況
//case1.在快取中找到圖片(代理不允許下載 或者沒有設定SDWebImageRefreshCached選項  滿足至少一項)
        else if (image) {
            //完成回撥
            dispatch_main_sync_safe(^{
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if (strongOperation && !strongOperation.isCancelled) {
                    completedBlock(image, nil, cacheType, YES, url);
                }
            });
          //從正在進行的操作列表中移除組合操作
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }
        }
          //case2:快取中沒有扎到圖片且代理不允許下載
        else {
        //主執行緒執行完成回撥
            dispatch_main_sync_safe(^{
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if (strongOperation && !weakOperation.isCancelled) {
                    completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
                }
            });
          //從正在執行的操作列表中移除組合操作
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }
        }
    }];

總結一下函數呼叫

1.先呼叫

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder;

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
  • 2.1 取消該控制元件對應的之前的所有的下載操作
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key;
  • 2.2 開始根據圖片的url做為key去查詢
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock

2.2.1 查詢記憶體和硬碟上的快取

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;
  • 2.3 建立下載佇列下載圖片
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
  • 2.4 最後將進行的操作,放到view對應的opationsDicaionary的字典中。記錄當前的操作佇列
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key

以上就是原始碼解析ios開發SDWebImage方法的詳細內容,更多關於ios SDWebImage方法的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com