美文网首页iOS源码解读知识点详解iOS Developer
SDWebImage源码解读之SDWebImagePrefetc

SDWebImage源码解读之SDWebImagePrefetc

作者: 老马的春天 | 来源:发表于2017-01-22 15:33 被阅读667次

    第十篇

    前言

    我们先看看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 简书 博客园

    相关文章

      网友评论

        本文标题:SDWebImage源码解读之SDWebImagePrefetc

        本文链接:https://www.haomeiwen.com/subject/bfrobttx.html