SDWebImage源码解读之SDWebImagePrefetcher

第十篇

前言

我们先看看SDWebImage主文件的组成模块:

可以看出来,每个模块即独立又相对关联,当最后拼接出SDWebImageManager的时候,我们就可以利用它来做一些有意思的事情。

本篇就主要讲解其中的一个使用场景:批量图片下载。记得之前有一位同学有这样的开发需求:他们公司要做一个漫画APP,漫画都是由图片组成的,每一个本漫画由很多章节组成,需要提供一个缓存功能,也就是把图片一组一组的下载下来。那么使用本篇的这个类就能完美的解决它的需求。

SDWebImagePrefetcherDelegate

这个代理提供了两个方法来监听事件:

  • 每次下载完一个图片
  • 所有的都下载完

代码:

@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属性,这个属性用来记录下载失败的次数,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];
      }
      }
      }
      这个函数很有意思,首先调用了[self cancelPrefetching],我们看看该方法的实现:

    • (void)cancelPrefetching {
      self.prefetchURLs = nil;
      self.skippedCount = 0;
      self.requestedCount = 0;
      self.finishedCount = 0;
      [self.manager cancelAll];
      }
      说明调用该方法后,所以的未完成的下载都会清空,也就是说SDWebImagePrefetcher只专注处理一组URLs,是无状态的下载。也就要求我们传入的URLs要完整。

那么我们如何实现支持多个图片并发下载呢?我们都知道SDWebImageManager的loadImage方法是异步执行的,因此只要多次调用loadImage方法就能做到了。也就是[self startPrefetchingAtIndex:i];

- (void)startPrefetchingAtIndex:(NSUInteger)index {
    /// 判断index是否越界
    if (index >= self.prefetchURLs.count) return;
    /// 已请求的个数加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) { // 下载成功后,调用progressBlock
            if (self.progressBlock) {
                self.progressBlock(self.finishedCount,(self.prefetchURLs).count);
            }
        }
        else { // 下载失败,也调用progressBlock,同时记录该次的下载失败
            if (self.progressBlock) {
                self.progressBlock(self.finishedCount,(self.prefetchURLs).count);
            }
            // Add last failed
            self.skippedCount++;
        }
        /// 调用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的数量大于已经下载的数量,就说明还有没下载完的任务,继续下载下一个
        if (self.prefetchURLs.count > self.requestedCount) {
            dispatch_async(self.prefetcherQueue, ^{
                [self startPrefetchingAtIndex:self.requestedCount];
            });
        } else if (self.finishedCount == self.requestedCount) { // 当完成数等于已请求总数的时候,就宣告下载完毕
            /// 告诉代理,下载已经完毕
            [self reportStatus];
            /// 调用completionBlock,这里把completionBlock和progressBlock都设为nil是为了避免循环引用
            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
         ];
    }
}

总结

SDWebImagePrefetcherSDWebImageManager很好的应用例子,下一篇我们总结一下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 简书 博客园
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容