读 SDWebImage 六 (SDWebImagePrefetcher)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @protocol SDWebImagePrefetcherDelegate <NSObject> @optional /** * 在预加载图片时调用。 * * @param imagePrefetcher 当前图片预加载类 * @param imageURL 预加载的图片网址 * @param finishedCount 预加载的图片总数(成功与否) * @param totalCount 预加载的图片总数 */ - (void)imagePrefetcher:(nonnull SDWebImagePrefetcher *)imagePrefetcher didPrefetchURL:(nullable NSURL *)imageURL finishedCount:(NSUInteger)finishedCount totalCount:(NSUInteger)totalCount; /** * 在预加载所有图像时调用。 * @param imagePrefetcher 当前图片的预加载类 * @param totalCount 预加载的图片总数(无论是否成功) * @param skippedCount 跳过的图片总数 */ - (void)imagePrefetcher:(nonnull SDWebImagePrefetcher *)imagePrefetcher didFinishWithTotalCount:(NSUInteger)totalCount skippedCount:(NSUInteger)skippedCount; @end
两个block代码块 1 2 3 4 5 6 7 8 9 10 11 12 13 /** 预加载进度block @param noOfFinishedUrls 已经完成的数量,无论成功失败 @param noOfTotalUrls 总数量 */ typedef void(^SDWebImagePrefetcherProgressBlock)(NSUInteger noOfFinishedUrls, NSUInteger noOfTotalUrls); /** 预加载完成block @param noOfFinishedUrls 已经完成的数量,无论成功失败 @param noOfSkippedUrls 跳过的数量 */ typedef void(^SDWebImagePrefetcherCompletionBlock)(NSUInteger noOfFinishedUrls, NSUInteger noOfSkippedUrls);
属性声明 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 /** * 网络图片管理器,只读 */ @property (strong, nonatomic, readonly, nonnull) SDWebImageManager *manager; /** *同时预加载的最大URL数。 默认为3。 */ @property (nonatomic, assign) NSUInteger maxConcurrentDownloads; /** * 预加载的SDWebImageOptions选项。 默认为SDWebImageLowPriority。 */ @property (nonatomic, assign) SDWebImageOptions options; /** * Prefetcher的队列选项。 默认为主队列。 */ @property (strong, nonatomic, nonnull) dispatch_queue_t prefetcherQueue; //协议 @property (weak, nonatomic, nullable) id <SDWebImagePrefetcherDelegate> delegate;
方法声明 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 /** * 返回全局图像预加载实例。 */ + (nonnull instancetype)sharedImagePrefetcher; /** * 允许您使用任意图像管理器实例化预加载类。 */ - (nonnull instancetype)initWithImageManager:(nonnull SDWebImageManager *)manager NS_DESIGNATED_INITIALIZER; /** * 分配URL列表以让SDWebImagePrefetcher对预加载进行排队 * 目前一次下载一张图片,并跳过下载失败的图像,然后进入列表中的下一个图像。任何先前运行的预加载操作都将被取消。 * @param urls 预加载的URL列表 */ - (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls; /** * 分配URL列表以让SDWebImagePrefetcher对预加载进行排队,目前一次下载一张图片,并跳过下载失败的图像,然后进入列表中的下一个图像。任何先前运行的预加载操作都将被取消。 * currently one image is downloaded at a time, * and skips images for failed downloads and proceed to the next image in the list. * Any previously-running prefetch operations are canceled. * * @param urls 预加载的URL列表 * @param progressBlock block块在进度更新时被调用; * 第一个参数是已完成(成功或未成功)请求的数量, * 第二个参数是最初请求预加载的图像总数 * @param completionBlock Block块在预加载完成时被调用 * 第一个参数是已完成(成功或未成功)请求的数量, * 第二个参数是跳过的请求数 */ - (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock; /** * 删除并取消排队列表 */ - (void)cancelPrefetching;
私有属性声明 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 //.h文件manager管理器声明为只读,这里声明为读写类型。实现外部只能访问,内部可读写。 @property (strong, nonatomic, nonnull) SDWebImageManager *manager; //可以从不同的队列访问 @property (strong, atomic, 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;
方法实现 初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 + (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; 预加载的SDWebImageOptions选项。 默认为SDWebImageLowPriority。 _options = SDWebImageLowPriority; //Prefetcher的队列选项。 默认为主队列。 _prefetcherQueue = dispatch_get_main_queue(); //同时预加载的最大URL数。 默认为3。 self.maxConcurrentDownloads = 3; } return self; }
属性get和set方法
1 2 3 4 5 6 7 8 9 //同时预加载的最大数的set方法 - (void)setMaxConcurrentDownloads:(NSUInteger)maxConcurrentDownloads { self.manager.imageDownloader.maxConcurrentDownloads = maxConcurrentDownloads; } //同时预加载的最大数的get方法 - (NSUInteger)maxConcurrentDownloads { return self.manager.imageDownloader.maxConcurrentDownloads; }
预加载图片实现方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 - (void)startPrefetchingAtIndex:(NSUInteger)index { //创建临时变量,用于存储当前的URL NSURL *currentURL; //同步锁,判断index是否越界,如果越界就返回,否则获取当前的URL,赋值给临时变量currentURL,已请求的数量+1 @synchronized(self) { if (index >= self.prefetchURLs.count) return; currentURL = self.prefetchURLs[index]; self.requestedCount++; } //图片管理器加载当前URL的图片 [self.manager loadImageWithURL:currentURL options:self.options progress:nil completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { //如果已经完成,返回 if (!finished) return; //完成的记数+1 self.finishedCount++; //如果需要过程回调,执行过程回调操作 if (self.progressBlock) { self.progressBlock(self.finishedCount,(self.prefetchURLs).count); } //如果图片不存在,跳过的记数+1 if (!image) { // Add last failed self.skippedCount++; } //如果delegate存在,执行delegate方法 if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didPrefetchURL:finishedCount:totalCount:)]) { [self.delegate imagePrefetcher:self didPrefetchURL:currentURL finishedCount:self.finishedCount totalCount:self.prefetchURLs.count ]; } //如果预下载的URLs数量大于已经下载的数量,就说明还有没下载完的任务,继续下载下一个。(这里是使用的递归方法) if (self.prefetchURLs.count > self.requestedCount) { dispatch_async(self.prefetcherQueue, ^{ // 我们需要调度来避免函数递归调用。 即使对于巨大的URL列表,这也可以防止堆栈溢出 [self startPrefetchingAtIndex:self.requestedCount]; }); } else if (self.finishedCount == self.requestedCount) { //如果预下载的URLs数量等于已经下载的数量,说明已经下载完。 //执行预加载完成的delegate方法。 [self reportStatus]; //如果有需要,执行完成block回调 if (self.completionBlock) { self.completionBlock(self.finishedCount, self.skippedCount); //将self.completionBlock 置空 self.completionBlock = nil; } //将self.progressBlock 置空 self.progressBlock = nil; } }]; } //预加载完成状态的delegate方法 - (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 ]; } }
分配URL列表使得SDWebImagePrefetcher来安排预加载队列,当前同一时间下载一张图片,忽略下载时间的图片并继续执行列表中的下一张图片。任何之前执行的预加载操作都会被取消。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 //urls:预加载的URL列表。 - (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls { [self prefetchURLs:urls progress:nil completed:nil]; } - (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock { //防止重复的预加载请求 [self cancelPrefetching]; //CFAbsoluteTimeGetCurrent() 返回网络时间同步的时钟时间 self.startedTime = CFAbsoluteTimeGetCurrent(); //预加载的URL列表 self.prefetchURLs = urls; 代码块 self.completionBlock = completionBlock; self.progressBlock = progressBlock; //如果预加载的URL列表为空,直接回调完成代码块,没有完成的urls和没有跳过的urls都传0 if (urls.count == 0) { if (completionBlock) { completionBlock(0,0); } } else { // 从具有最大允许并发性的列表中的第一个图像开始预加载,然后执行预加载操作 NSUInteger listCount = self.prefetchURLs.count; for (NSUInteger i = 0; i < self.maxConcurrentDownloads && self.requestedCount < listCount; i++) { [self startPrefetchingAtIndex:i]; } } }
防止重复的预加载请求
1 2 3 4 5 6 7 8 9 - (void)cancelPrefetching { @synchronized(self) { self.prefetchURLs = nil; self.skippedCount = 0; self.requestedCount = 0; self.finishedCount = 0; } [self.manager cancelAll]; }