SDWebImage源码解读之SDWebImagePrefetcher

> 第十篇

## 前言
我们先看看`SDWebImage`主文件的组成模块:

![](http://images2015.cnblogs.com/blog/637318/201701/637318-20170122150927488-934404619.png)

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

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

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

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

代码:

@protocol SDWebImagePrefetcherDelegate

@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 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 *)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 *)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 *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 *)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.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
];
}
}

## 总结
`SDWebImagePrefetcher`是`SDWebImageManager`很好的应用例子,下一篇我们总结一下UI控件使用`SDWebImageManager`获取图片的例子。

**由于个人知识有限,如有错误之处,还望各路大侠给予指出啊**

1. SDWebImage源码解读 之 NSData+ImageContentType [简书](http://www.jianshu.com/p/fd984fd8bd5d) [博客园](http://www.cnblogs.com/machao/p/6126826.html)
2. SDWebImage源码解读 之 UIImage+GIF [简书](http://www.jianshu.com/p/d3e9e3d0a778) [博客园](http://www.cnblogs.com/machao/p/6134364.html)
3. SDWebImage源码解读 之 SDWebImageCompat [简书](http://www.jianshu.com/p/1d2e4d822732) [博客园](http://www.cnblogs.com/machao/p/6137517.html)
4. SDWebImage源码解读 之SDWebImageDecoder [简书](http://www.jianshu.com/p/9322acb7a7b1) [博客园](http://www.cnblogs.com/machao/p/6150636.html)
5. SDWebImage源码解读 之SDWebImageCache(上) [简书](http://www.jianshu.com/p/b3eff8304b37) [博客园](http://www.cnblogs.com/machao/p/6179638.html)
6. SDWebImage源码解读之SDWebImageCache(下) [简书](http://www.jianshu.com/p/a33d5abf686b) [博客园](http://www.cnblogs.com/machao/p/6198140.html)
7. SDWebImage源码解读之SDWebImageDownloaderOperation [简书](http://www.jianshu.com/p/662c09582d30) [博客园](http://www.cnblogs.com/machao/p/6248111.html)
8. SDWebImage源码解读之SDWebImageDownloader [简书](http://www.jianshu.com/p/8411e4645f0d) [博客园](http://www.cnblogs.com/machao/p/6265621.html)
9. SDWebImage源码解读之SDWebImageManager [简书](http://www.jianshu.com/p/a2cc208ee016) [博客园](http://www.cnblogs.com/machao/p/6323337.html)

上一篇:执行 update操作的时候有报错 ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction


下一篇:SDWebImage源码解读之分类