SDWebImage源码解读之SDWebImagePrefetcher
2017-01-22 15:38
405 查看
第十篇
前言
我们先看看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; }
setter,getter:
- (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 简书 博客园
相关文章推荐
- SDWebImage源码解读 之SDWebImagePrefetcher
- SDWebImage源码解读之SDWebImageCache(下)
- SDWebImage源码解读之SDWebImageManager
- SDWebImage源码解读 之SDWebImageCache(下)
- SDWebImage源码解读 之SDWebImageCache(上)
- iOS - SDWebImage源码解读,分解流程,超细注解,进阶必备
- SDWebImage源码解读之SDWebImageDownloaderOperation
- SDWebImage源码解读 之SDWebImageDownloader
- SDWebImage源码解读之干货大总结
- SDWebImage源码解读之 分类
- SDWebImage源码解读之分类
- SDWebImage源码解读 之 NSData+ImageContentType
- SDWebImage源码解读,分解流程,超细注解,进阶必备
- SDWebImage源码解读 之SDWebImageDecoder
- SDWebImage源码解读之获取图片格式
- SDWebImage源码解读_之SDWebImageDecoder
- SDWebImage源码解读之NSData+ImageContentType
- SDWebImage源码解读 之UIImage+GIF
- SDWebImage源码解读之SDWebImageDownloader
- SDWebImage源码解读之干货大总结