SDWebImage源碼解讀之SDWebImagePrefetcher

第十篇

前言

我們先看看SDWebImage主文件的組成模塊:

可以看出來倦逐,每個模塊即獨立又相對關聯(lián),當最后拼接出SDWebImageManager的時候城须,我們就可以利用它來做一些有意思的事情质和。

本篇就主要講解其中的一個使用場景:批量圖片下載火鼻。記得之前有一位同學有這樣的開發(fā)需求:他們公司要做一個漫畫APP隶垮,漫畫都是由圖片組成的藻雪,每一個本漫畫由很多章節(jié)組成,需要提供一個緩存功能狸吞,也就是把圖片一組一組的下載下來阔涉。那么使用本篇的這個類就能完美的解決它的需求。

SDWebImagePrefetcherDelegate

這個代理提供了兩個方法來監(jiān)聽事件:

  • 每次下載完一個圖片
  • 所有的都下載完

代碼:

@protocol SDWebImagePrefetcherDelegate <NSObject>

@optional

/**
 * Called when an image was prefetched.
 *
 * @param imagePrefetcher The current image prefetcher
 * @param imageURL        The image url that was prefetched
 * @param finishedCount   The total number of images that were prefetched (successful or not)
 * @param totalCount      The total number of images that were to be prefetched
 */
- (void)imagePrefetcher:(nonnull SDWebImagePrefetcher *)imagePrefetcher didPrefetchURL:(nullable NSURL *)imageURL finishedCount:(NSUInteger)finishedCount totalCount:(NSUInteger)totalCount;

/**
 * Called when all images are prefetched.
 * @param imagePrefetcher The current image prefetcher
 * @param totalCount      The total number of images that were prefetched (whether successful or not)
 * @param skippedCount    The total number of images that were skipped
 */
- (void)imagePrefetcher:(nonnull SDWebImagePrefetcher *)imagePrefetcher didFinishWithTotalCount:(NSUInteger)totalCount skippedCount:(NSUInteger)skippedCount;

@end

SDWebImagePrefetcher.h

屬性:

/**
 *  The web image manager
 */
@property (strong, nonatomic, readonly, nonnull) SDWebImageManager *manager;

/**
 * Maximum number of URLs to prefetch at the same time. Defaults to 3.
 */
@property (nonatomic, assign) NSUInteger maxConcurrentDownloads;

/**
 * SDWebImageOptions for prefetcher. Defaults to SDWebImageLowPriority.
 */
@property (nonatomic, assign) SDWebImageOptions options;

/**
 * Queue options for Prefetcher. Defaults to Main Queue.
 */
@property (nonatomic, assign, nonnull) dispatch_queue_t prefetcherQueue;

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

初始化:

/**
 * Return the global image prefetcher instance.
 */
+ (nonnull instancetype)sharedImagePrefetcher;

/**
 * Allows you to instantiate a prefetcher with any arbitrary image manager.
 */
- (nonnull instancetype)initWithImageManager:(nonnull SDWebImageManager *)manager NS_DESIGNATED_INITIALIZER;

方法:

/**
 * Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching,
 * currently one image is downloaded at a time,
 * and skips images for failed downloads and proceed to the next image in the list
 *
 * @param urls list of URLs to prefetch
 */
- (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls;

/**
 * Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching,
 * currently one image is downloaded at a time,
 * and skips images for failed downloads and proceed to the next image in the list
 *
 * @param urls            list of URLs to prefetch
 * @param progressBlock   block to be called when progress updates; 
 *                        first parameter is the number of completed (successful or not) requests, 
 *                        second parameter is the total number of images originally requested to be prefetched
 * @param completionBlock block to be called when prefetching is completed
 *                        first param is the number of completed (successful or not) requests,
 *                        second parameter is the number of skipped requests
 */
- (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls
            progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock
           completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock;

/**
 * Remove and cancel queued list
 */
- (void)cancelPrefetching;

SDWebImagePrefetcher.m

@interface SDWebImagePrefetcher ()

@property (strong, nonatomic, nonnull) SDWebImageManager *manager;
@property (strong, nonatomic, nullable) NSArray<NSURL *> *prefetchURLs;
@property (assign, nonatomic) NSUInteger requestedCount;
@property (assign, nonatomic) NSUInteger skippedCount;
@property (assign, nonatomic) NSUInteger finishedCount;
@property (assign, nonatomic) NSTimeInterval startedTime;
@property (copy, nonatomic, nullable) SDWebImagePrefetcherCompletionBlock completionBlock;
@property (copy, nonatomic, nullable) SDWebImagePrefetcherProgressBlock progressBlock;

@end

這里多了一個skippedCount屬性捷绒,這個屬性用來記錄下載失敗的次數(shù),skip表示跳過的意思贯要。

+ (nonnull instancetype)sharedImagePrefetcher {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}

- (nonnull instancetype)init {
    return [self initWithImageManager:[SDWebImageManager new]];
}

- (nonnull instancetype)initWithImageManager:(SDWebImageManager *)manager {
    if ((self = [super init])) {
        _manager = manager;
        _options = SDWebImageLowPriority;
        _prefetcherQueue = dispatch_get_main_queue();
        self.maxConcurrentDownloads = 3;
    }
    return self;
}
    • (void)setMaxConcurrentDownloads:(NSUInteger)maxConcurrentDownloads {
      self.manager.imageDownloader.maxConcurrentDownloads = maxConcurrentDownloads;
      }

    • (NSUInteger)maxConcurrentDownloads {
      return self.manager.imageDownloader.maxConcurrentDownloads;
      }
      這里是setter和getter方法暖侨,有意思的是setter并不以一定要給這個屬性賦值,getter而不一定就一定返回該屬性的值崇渗。

    • (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls
      progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock
      completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock {
      [self cancelPrefetching]; // Prevent duplicate prefetch request
      self.startedTime = CFAbsoluteTimeGetCurrent();
      self.prefetchURLs = urls;
      self.completionBlock = completionBlock;
      self.progressBlock = progressBlock;

      if (urls.count == 0) {
      if (completionBlock) {
      completionBlock(0,0);
      }
      } else {
      // Starts prefetching from the very first image on the list with the max allowed concurrency
      NSUInteger listCount = self.prefetchURLs.count;
      for (NSUInteger i = 0; i < self.maxConcurrentDownloads && self.requestedCount < listCount; i++) {
      [self startPrefetchingAtIndex:i];
      }
      }
      }
      這個函數(shù)很有意思,首先調(diào)用了[self cancelPrefetching],我們看看該方法的實現(xiàn):

    • (void)cancelPrefetching {
      self.prefetchURLs = nil;
      self.skippedCount = 0;
      self.requestedCount = 0;
      self.finishedCount = 0;
      [self.manager cancelAll];
      }
      說明調(diào)用該方法后,所以的未完成的下載都會清空宙搬,也就是說SDWebImagePrefetcher只專注處理一組URLs裆针,是無狀態(tài)的下載。也就要求我們傳入的URLs要完整跟狱。

那么我們?nèi)绾螌崿F(xiàn)支持多個圖片并發(fā)下載呢俭厚?我們都知道SDWebImageManager的loadImage方法是異步執(zhí)行的,因此只要多次調(diào)用loadImage方法就能做到了驶臊。也就是[self startPrefetchingAtIndex:i];

- (void)startPrefetchingAtIndex:(NSUInteger)index {
    /// 判斷index是否越界
    if (index >= self.prefetchURLs.count) return;
    /// 已請求的個數(shù)加1
    self.requestedCount++;
    /// 使用self.manager下載圖片
    [self.manager loadImageWithURL:self.prefetchURLs[index] options:self.options progress:nil completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
        /// 只有當finished完成之后挪挤,self.finishedCount加1
        if (!finished) return;
        self.finishedCount++;

        if (image) { // 下載成功后叼丑,調(diào)用progressBlock
            if (self.progressBlock) {
                self.progressBlock(self.finishedCount,(self.prefetchURLs).count);
            }
        }
        else { // 下載失敗,也調(diào)用progressBlock扛门,同時記錄該次的下載失敗
            if (self.progressBlock) {
                self.progressBlock(self.finishedCount,(self.prefetchURLs).count);
            }
            // Add last failed
            self.skippedCount++;
        }
        /// 調(diào)用delegate
        if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didPrefetchURL:finishedCount:totalCount:)]) {
            [self.delegate imagePrefetcher:self
                            didPrefetchURL:self.prefetchURLs[index]
                             finishedCount:self.finishedCount
                                totalCount:self.prefetchURLs.count
             ];
        }
        /// 如果URLs的數(shù)量大于已經(jīng)下載的數(shù)量鸠信,就說明還有沒下載完的任務,繼續(xù)下載下一個
        if (self.prefetchURLs.count > self.requestedCount) {
            dispatch_async(self.prefetcherQueue, ^{
                [self startPrefetchingAtIndex:self.requestedCount];
            });
        } else if (self.finishedCount == self.requestedCount) { // 當完成數(shù)等于已請求總數(shù)的時候论寨,就宣告下載完畢
            /// 告訴代理星立,下載已經(jīng)完畢
            [self reportStatus];
            /// 調(diào)用completionBlock,這里把completionBlock和progressBlock都設為nil是為了避免循環(huán)引用
            if (self.completionBlock) {
                self.completionBlock(self.finishedCount, self.skippedCount);
                self.completionBlock = nil;
            }
            self.progressBlock = nil;
        }
    }];
}

- (void)reportStatus {
    NSUInteger total = (self.prefetchURLs).count;
    if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didFinishWithTotalCount:skippedCount:)]) {
        [self.delegate imagePrefetcher:self
               didFinishWithTotalCount:(total - self.skippedCount)
                          skippedCount:self.skippedCount
         ];
    }
}

總結(jié)

SDWebImagePrefetcherSDWebImageManager很好的應用例子,下一篇我們總結(jié)一下UI控件使用SDWebImageManager獲取圖片的例子葬凳。

由于個人知識有限绰垂,如有錯誤之處,還望各路大俠給予指出啊

  1. SDWebImage源碼解讀 之 NSData+ImageContentType 簡書 博客園
  2. SDWebImage源碼解讀 之 UIImage+GIF 簡書 博客園
  3. SDWebImage源碼解讀 之 SDWebImageCompat 簡書 博客園
  4. SDWebImage源碼解讀 之SDWebImageDecoder 簡書 博客園
  5. SDWebImage源碼解讀 之SDWebImageCache(上) 簡書 博客園
  6. SDWebImage源碼解讀之SDWebImageCache(下) 簡書 博客園
  7. SDWebImage源碼解讀之SDWebImageDownloaderOperation 簡書 博客園
  8. SDWebImage源碼解讀之SDWebImageDownloader 簡書 博客園
  9. SDWebImage源碼解讀之SDWebImageManager 簡書 博客園
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末沮明,一起剝皮案震驚了整個濱河市辕坝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荐健,老刑警劉巖酱畅,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異江场,居然都是意外死亡纺酸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門址否,熙熙樓的掌柜王于貴愁眉苦臉地迎上來餐蔬,“玉大人,你說我怎么就攤上這事佑附》担” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵音同,是天一觀的道長词爬。 經(jīng)常有香客問我,道長权均,這世上最難降的妖魔是什么顿膨? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮叽赊,結(jié)果婚禮上恋沃,老公的妹妹穿的比我還像新娘。我一直安慰自己必指,他們只是感情好囊咏,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般匆笤。 火紅的嫁衣襯著肌膚如雪研侣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天炮捧,我揣著相機與錄音庶诡,去河邊找鬼。 笑死咆课,一個胖子當著我的面吹牛末誓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播书蚪,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼喇澡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了殊校?” 一聲冷哼從身側(cè)響起晴玖,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎为流,沒想到半個月后呕屎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡敬察,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年秀睛,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片莲祸。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡蹂安,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锐帜,到底是詐尸還是另有隱情田盈,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布缴阎,位于F島的核電站缠黍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏药蜻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一替饿、第九天 我趴在偏房一處隱蔽的房頂上張望语泽。 院中可真熱鬧,春花似錦视卢、人聲如沸踱卵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惋砂。三九已至妒挎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間西饵,已是汗流浹背酝掩。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留眷柔,地道東北人期虾。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像驯嘱,于是被迫代替她去往敵國和親镶苞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

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