第十篇
前言
我们先看看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
];
}
}
总结
SDWebImagePrefetcher
是SDWebImageManager
很好的应用例子,下一篇我们总结一下UI控件使用SDWebImageManager
获取图片的例子。
由于个人知识有限,如有错误之处,还望各路大侠给予指出啊
- SDWebImage源码解读 之 NSData+ImageContentType 简书 博客园
- SDWebImage源码解读 之 UIImage+GIF 简书 博客园
- SDWebImage源码解读 之 SDWebImageCompat 简书 博客园
- SDWebImage源码解读 之SDWebImageDecoder 简书 博客园
- SDWebImage源码解读 之SDWebImageCache(上) 简书 博客园
- SDWebImage源码解读之SDWebImageCache(下) 简书 博客园
- SDWebImage源码解读之SDWebImageDownloaderOperation 简书 博客园
- SDWebImage源码解读之SDWebImageDownloader 简书 博客园
- SDWebImage源码解读之SDWebImageManager 简书 博客园