SDWebImage源碼解讀之SDWebImagePrefetcher

第十篇

前言

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

可以看出來,每個模塊即獨立又相對關(guān)聯(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 簡書 博客園
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容