SDWebImagePrefetcher 预先下载(预加载)图片,以方便后续使用,主要是用于部分图片需要先行下载并存储的情况。预先取出缓存中的一些URLs以供将来使用。 图像以低优先级下载。

SDWebImagePrefetcher 设计了两种回调方式:

  1. SDWebImagePrefetcherDelegate
    用来处理每一个预下载完成的回调,以及所有下载完成的回调

  2. block
    用来处理整体进度的回调,返回的是下载完成的数量和总数量等

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