每日一問27——SDWebImage(調(diào)度)

前言

通過前兩篇關(guān)于SDWebImage的學(xué)習(xí)著隆,我們已經(jīng)知道了它的下載策略和緩存策略。本次要學(xué)習(xí)的內(nèi)容是SDWebImage中是通過怎樣的調(diào)度方法來使用下載和緩存的美浦。

方法的調(diào)用

在使用的時候浦辨,我們通常會直接調(diào)用類別中提供的方法來加載圖片,查看它們的實現(xiàn)我們可以發(fā)現(xiàn)最終我們會調(diào)用到這樣一個函數(shù)

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

外部提供的接口都是對這個函數(shù)的一個封裝币厕,方便我們使用時候調(diào)用芽腾。來看看具體的實現(xiàn):

里面首先對operationKey進行了檢驗,生成可用的key摊滔,并將這個key對應(yīng)的operation添加到了一個SDOperationsDictionary類的字典中店乐,并綁定到對應(yīng)的view中呻袭。后續(xù)在管理任務(wù)都會用到這個字典。

NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

省略一些檢測代碼廉侧,當(dāng)URL正確時篓足,就會調(diào)用SDWebImageManager類的單利對象進行圖片下載。這個方法就會返回一個operation回來枕扫,通過[self sd_setImageLoadOperation:operation forKey:validOperationKey];將這個operation保存到之前說到的字典中辱魁。

id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
        /**
        省略若干代碼诗鸭,這些代碼的作用是根據(jù)用戶的設(shè)定將下載好的數(shù)據(jù)返回給上層。
        *//
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
}

可以發(fā)現(xiàn)锻弓,這個函數(shù)中并沒有下載和緩存相關(guān)的代碼蝌箍,于是我們可以猜測下載和緩存相關(guān)的邏輯是放在SDWebImageManager類中進行處理的。下面我們就對調(diào)度類SDWebImageManager進行進一步的查看分析妓盲。

SDWebImageManager

查看SDWebImageManager.h文件,可以發(fā)現(xiàn)SDWebImageManager中提供了2個我們之前講到過的屬性弹沽,分別是SDWebImageDownloaderimageCache筋粗。這兩個實例中不就是提供下載和緩存的操作嗎?

@interface SDWebImageManager : NSObject

@property (weak, nonatomic, nullable) id <SDWebImageManagerDelegate> delegate;

@property (strong, nonatomic, readonly, nullable) SDImageCache *imageCache;
@property (strong, nonatomic, readonly, nullable) SDWebImageDownloader *imageDownloader;

進一步查看丽已,還提供了一個遵循<SDWebImageManagerDelegate>協(xié)議的代理买决。<SDWebImageManagerDelegate>協(xié)議主要提供了2個接口:

//控制在cache中沒有找到image時 是否應(yīng)該去下載吼畏。默認(rèn)是YES瘸味。
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nullable NSURL *)imageURL;

//在下載之后,緩存之前轉(zhuǎn)換圖片藕夫。在全局隊列中操作枯冈,不阻塞主線程
- (nullable UIImage *)imageManager:(nonnull SDWebImageManager *)imageManager transformDownloadedImage:(nullable UIImage *)image withURL:(nullable NSURL *)imageURL;

此外,SDWebImageManager還提供了這些操作尘奏,包括圖片的下載,取消炫加,緩存,檢測等酒甸。我們需要重點學(xué)習(xí)的方法是downloadImageWithURL下載這個函數(shù)赋铝。

- (instancetype)initWithCache:(SDImageCache *)cache downloader:(SDWebImageDownloader *)downloader;
//下載圖片
- (id )downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
//緩存給定URL的圖片
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
//取消當(dāng)前所有的操作
- (void)cancelAll;
//監(jiān)測當(dāng)前是否有進行中的操作
- (BOOL)isRunning;
//監(jiān)測圖片是否在緩存中,監(jiān)測結(jié)束后調(diào)用completionBlock
- (void)cachedImageExistsForURL:(NSURL *)url
                     completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
//監(jiān)測圖片是否緩存在disk里,監(jiān)測結(jié)束后調(diào)用completionBlock
- (void)diskImageExistsForURL:(NSURL *)url
                   completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
//返回給定URL的cache key,默認(rèn)是圖片的url
- (NSString *)cacheKeyForURL:(NSURL *)url;

對于downloadImageWithURL下載函數(shù)來說农尖,它需要傳入4個參數(shù)良哲,和返回SDWebImageOperation類的實例。

  • @param url 網(wǎng)絡(luò)圖片的 url 地址
  • @param options 一些定制化選項
  • @param progressBlock 下載時的 Block窟扑,其定義為:typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);
  • @param completedBlock 下載完成時的 Block漏健,其定義為:typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished);
  • @return 返回 SDWebImageOperation 的實例

具體的流程是

1.驗證URL正確性

如果不正確,則回調(diào)error

if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }

    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;

    BOOL isFailedUrl = NO;
    if (url) {
        @synchronized (self.failedURLs) {
            isFailedUrl = [self.failedURLs containsObject: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;
    }
2.檢測該URL是否被下載過殖属,去緩存中查找圖片

私有成員變量是一個NSMutableArray<SDWebImageCombinedOperation *>的數(shù)組瓦盛,用來保存所有正在進行的operation外潜,方便統(tǒng)一控制挠唆。

@synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
//根據(jù)url生成緩存對應(yīng)的key
    NSString *key = [self cacheKeyForURL:url];

//將查找結(jié)果保存到operation中的chcheOperation中。
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        if (operation.isCancelled) {
            [self safelyRemoveOperationFromRunning:operation];
            return;
        }
3.根據(jù)不同的情況進行不同的操作
3.1>如果在緩存中沒有找到圖片滔驾,或者采用的 SDWebImageRefreshCached 選項俄讹,則從網(wǎng)絡(luò)下載

這個操作會先調(diào)用imageDownloader嘗試下載圖片,如果下載失敗則拋出異常摊阀,如果下載成功則會調(diào)用imageCache將圖片進行緩存踪蹬。

//如果沒有緩存或者用戶允許刷新緩存
if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {

//如果在緩存中找到了圖片,直接回調(diào)跃捣。
            if (cachedImage && options & SDWebImageRefreshCached) {
                [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }

           /**
            省略一部分用戶定制策略的判斷
            **/

            //使用imageDownloader進行下載枝缔。
            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) {
                    
                    //如果這個任務(wù)被取消了蚊惯,則什么都不做

                } else if (error) {
                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
                    如果這個任務(wù)失敗了,則組裝error對象趴荸,拋出
                    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) {
                            //將這個URL添加到失敗URL數(shù)組中
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {
                    //下載成功宦焦,如果url曾經(jīng)失敗過,則將這個url從失敗數(shù)組中移除
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                    if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                       //只是刷新緩存酝豪,則不回調(diào)

                    } 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), ^{
                            //如果下載成功精堕,并且不是GIF圖片,并且代理實現(xiàn)了圖片轉(zhuǎn)換
                            //則先使用代理進行圖片轉(zhuǎn)換
                            //將裝換后的結(jié)果進行緩存
                            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];
                            }
                            
                            //將結(jié)果回調(diào)
                            [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];
                        }
                            //回調(diào)
                        [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                    }
                }

                if (finished) {
                    //完畢后揉阎,將operation從進行中數(shù)組中移除
                    [self safelyRemoveOperationFromRunning:strongOperation];
                }
            }];
            @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];
                };
            }
3.2>有緩存則直接返回
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];
        }
3.3>沒有緩存背捌,用戶又不允許下載,則返回nil并講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];
        }
其他接口

取消所有任務(wù)扭仁,從正在執(zhí)行的任務(wù)數(shù)組中獲取所有任務(wù)對象,并調(diào)用它們的cancel方法

- (void)cancelAll {
    @synchronized (self.runningOperations) {
        NSArray<SDWebImageCombinedOperation *> *copiedOperations = [self.runningOperations copy];
        [copiedOperations makeObjectsPerformSelector:@selector(cancel)];
        [self.runningOperations removeObjectsInArray:copiedOperations];
    }
}

//檢測sd當(dāng)前是否正在執(zhí)行任務(wù)

- (BOOL)isRunning {
    BOOL isRunning = NO;
    @synchronized (self.runningOperations) {
        isRunning = (self.runningOperations.count > 0);
    }
    return isRunning;
}

//安全移除任務(wù)搀突,由于runningOperations可能被多個線程同時訪問熊泵,所以需要進行加鎖

- (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation {
    @synchronized (self.runningOperations) {
        if (operation) {
            [self.runningOperations removeObject:operation];
        }
    }
}
小結(jié):
  • 調(diào)度層下載的主要流程是什么

查找緩存,若緩存中沒有 image 則通過 SDWebImageDownloader 來進行下載顽分,下載完成后通過 SDImageCache 進行緩存,會同時緩存到 memCache 和 diskCache 中

  • 為什么下載成功要將url從失敗數(shù)組中移除卒蘸。

因為[self.failedURLs addObject:url]是只在下載失敗時添加的,而下載成功和下載失敗是互斥的恰起,也就是說趾牧,下載成功時failedURLs數(shù)組里就不應(yīng)該有這個url,為什么要這么寫呢翘单,這是為了解決競態(tài)條件下的問題,若兩個線程下載同一個url的圖片貌亭,若第一個線程下載失敗认臊,第二個下載成功。如果不從failedURLs移除這個url的話,以后下載此url的圖片都會失敗斟薇。

  • 為什么要返回返回的SDWebImageCombinedOperation類型

這個類型包含NSOperation *cacheOperation的一個子類型恕酸,其中cacheOperation中又存在id <SDWebImageOperation>的下載圖片的subOperation。在cancel的時候也應(yīng)該把這兩個操作都cancle蕊温。

相關(guān)文章

SDWebImage源碼閱讀筆記
SDWebImage 源碼閱讀筆記(一)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末义矛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子凉翻,更是在濱河造成了極大的恐慌,老刑警劉巖前计,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件垃杖,死亡現(xiàn)場離奇詭異,居然都是意外死亡伶棒,警方通過查閱死者的電腦和手機彩库,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鞭达,你說我怎么就攤上這事〕氩洌” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵繁扎,是天一觀的道長。 經(jīng)常有香客問我爹梁,道長提澎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任积糯,我火速辦了婚禮谦纱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘跨嘉。我一直安慰自己,他們只是感情好窘游,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布跳纳。 她就那樣靜靜地躺著,像睡著了一般寺庄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赢织,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天馍盟,我揣著相機與錄音,去河邊找鬼八毯。 笑死瞄桨,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芯侥。 我是一名探鬼主播乳讥,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼廓俭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了留晚?” 一聲冷哼從身側(cè)響起告嘲,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赋焕,沒想到半個月后仰楚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體隆判,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡侨嘀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年捂襟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涨共。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡宠漩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出扒吁,到底是詐尸還是另有隱情,我是刑警寧澤魁索,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布晨逝,位于F島的核電站懦铺,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜牧挣,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一醒陆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寺晌,春花似錦、人聲如沸呻征。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽攒岛。三九已至胞锰,卻和暖如春灾锯,著一層夾襖步出監(jiān)牢的瞬間胜蛉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工领突, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留案怯,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓金砍,卻偏偏與公主長得像麦锯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子扶欣,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

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