zoukankan      html  css  js  c++  java
  • 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;
    }
    

    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
             ];
        }
    }
    

    总结

    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 简书 博客园
  • 相关阅读:
    C#基于引用创建单链表
    锻炼自己的思维模式
    [数据结构]C#基于数组实现泛型顺序表
    DEV Express
    [LeetCode] Combinations (bfs bad、dfs 递归 accept)
    [LeetCode] Wildcard Matching
    [LeetCode] Remove Duplicates from Sorted List II
    [LeetCode] Partition List
    [LeetCode] Scramble String(树的问题最易用递归)
    [LeetCode] Decode Ways(DP)
  • 原文地址:https://www.cnblogs.com/machao/p/6340493.html
Copyright © 2011-2022 走看看