AFImageDownloader 这个类对写DownloadManager有很大的借鉴意义。在平时的开发中,当我们使用UIImageView加载一个网络上的图片时,其原理就是把图片下载下来,然后再赋值。这也是AFImageDownloader这个类的核心功能。
前言
AFImageDownloader 专门管理一组图片的下载任务。试想,如果有一个界面的功能是对图片进行处理,要求下载高清图片,那么这个AFImageDownloader就用上了。
1.AFImageDownloadPrioritization
AFImageDownloadPrioritization 表示图片下载的优先级:
- AFImageDownloadPrioritizationFIFO 表示先进先出
- AFImageDownloadPrioritizationLIFO 表示后进先出
2.AFImageDownloadReceipt
AFImageDownloadReceipt 表示一个下载依据。也就是对一个下载任务的封装。它有2个属性:
NSURLSessionDataTask *task;
NSUUID *receiptID;
其中 task 表示一个下载任务。一般来说,如果我们要对下载的任务进行操作,就是用这个 task 来完成,并不使用AFImageDownloader来操作。通过[NSUUID UUID]
来生成一个唯一的标识来证明AFImageDownloadReceipt的身份。
3.AFImageDownloaderResponseHandler
AFImageDownloaderResponseHandler 用来处理图片下载完成后的事件。他有三个属性:
-
@property (nonatomic, strong) NSUUID *uuid;
和AFImageDownloadReceip的receiptID一致 -
void (^successBlock)(NSURLRequest*, NSHTTPURLResponse*, UIImage*);
下载成功后的回调 -
void (^failureBlock)(NSURLRequest*, NSHTTPURLResponse*, NSError*);
下载失败后的回调
这个AFImageDownloaderResponseHandler在下边的使用中会被加到一个数组中。如果同一个下载请求写了两个Handler,那么这两个Handler会按顺序依次调用。这是后话,下边也会说到。值得注意的是:不论请求成功还是失败,都是在主线程调用的。
- (instancetype)initWithUUID:(NSUUID *)uuid
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure {
if (self = [self init]) {
self.uuid = uuid;
self.successBlock = success;
self.failureBlock = failure;
}
return self;
}
--
- (NSString *)description {
return [NSString stringWithFormat: @"UUID: %@", [self.uuid UUIDString]];
}
4.AFImageDownloaderMergedTask
AFImageDownloaderMergedTask 封装了对同一个请求的处理。同下载任务一样,也存在下载的URL重复的情况,这种情况需要做特殊处理。这里是一个值得借鉴的地方。那么如何判定是一个请求呢?AFImageDownloader是通过request.URL.absoluteString
来进行判断的。
我们看看它的属性有哪些?
-
NSString *URLIdentifier
URL标识 -
NSUUID *identifier
唯一标识 -
NSURLSessionDataTask *task
任务task -
NSMutableArray
回调的数组,上边已经解释过了。如果两个请求是相同的。会请求同一份数据,但是两个请求的回调都会调用。调用的顺序就是按照这个数组的排列顺序。*responseHandlers
--
- (instancetype)initWithURLIdentifier:(NSString *)URLIdentifier identifier:(NSUUID *)identifier task:(NSURLSessionDataTask *)task {
if (self = [self init]) {
self.URLIdentifier = URLIdentifier;
self.task = task;
self.identifier = identifier;
self.responseHandlers = [[NSMutableArray alloc] init];
}
return self;
}
--
- (void)addResponseHandler:(AFImageDownloaderResponseHandler*)handler {
[self.responseHandlers addObject:handler];
}
- (void)removeResponseHandler:(AFImageDownloaderResponseHandler*)handler {
[self.responseHandlers removeObject:handler];
}
5.AFImageDownloader
AFImageDownloader是构成获取图片数据的核心方法。我们可以设置图片的最大并发数。其中最需要注意的就是如何处理多次重复的请求和请求后数据的去处问题。我们先了解下AFImageDownloader暴露出来的属性:
-
id
图片缓存的地方imageCache -
AFHTTPSessionManager *sessionManager
通过这个属性来获取图片数据 -
AFImageDownloadPrioritization downloadPrioritizaton
下载的优先级。 -
initWithSessionManager:downloadPrioritization:maximumActiveDownloads:imageCache:
初始化的时候配置 downloadImageForURLRequest:success:failure:
-
downloadImageForURLRequest:withReceiptID:success:failure:
指定receiptID -
cancelTaskForImageDownloadReceipt:
取消一个请求
在AFImageDownloader.m中新增了下边这几个属性:
dispatch_queue_t synchronizationQueue
dispatch_queue_t responseQueue
-
NSInteger maximumActiveDownloads
支持的最大的同时下载数 -
NSInteger activeRequestCount
激活的请求数 -
NSMutableArray *queuedMergedTasks
队列中的task -
NSMutableDictionary *mergedTasks
合并的队列
补充1:
大概说下 dispatch_sync() 和 dispatch_async() 这两个方法。
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_sync(concurrentQueue, ^(){
NSLog(@"2");
[NSThread sleepForTimeInterval:5];
NSLog(@"3");
});
NSLog(@"4");
输出为:
2016-08-25 11:50:51.601 xxxx[1353:102804] 1
2016-08-25 11:50:51.601 xxxx[1353:102804] 2
2016-08-25 11:50:56.603 xxxx[1353:102804] 3
2016-08-25 11:50:56.603 xxxx[1353:102804] 4
再看:
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(concurrentQueue, ^(){
NSLog(@"2");
[NSThread sleepForTimeInterval:5];
NSLog(@"3");
});
NSLog(@"4");
输出为:
2016-08-25 11:52:29.022 xxxx[1392:104246] 1
2016-08-25 11:52:29.023 xxxx[1392:104246] 4
2016-08-25 11:52:29.023 xxxx[1392:104284] 2
2016-08-25 11:52:34.029 xxxx[1392:104284] 3
通过上边的两个例子,我们可以总结出:
- dispatch_sync(),同步添加操作。他是等待添加进队列里面的操作完成之后再继续执行。
- dispatch_async ,异步添加进任务队列,它不会做任何等待
补充2:
我们简单介绍下NSURLCache。NSURLCache 为您的应用的 URL 请求提供了内存中以及磁盘上的综合缓存机制。网络缓存减少了需要向服务器发送请求的次数,同时也提升了离线或在低速网络中使用应用的体验。当一个请求完成下载来自服务器的回应,一个缓存的回应将在本地保存。下一次同一个请求再发起时,本地保存的回应就会马上返回,不需要连接服务器。NSURLCache 会 自动 且 透明 地返回回应。
为了好好利用 NSURLCache,你需要初始化并设置一个共享的 URL 缓存。在 iOS 中这项工作需要在 -application:didFinishLaunchingWithOptions: 完成,而 OS X 中是在 –applicationDidFinishLaunching::
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
diskCapacity:20 * 1024 * 1024
diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];
}
NSURLRequest 有个 cachePolicy 属性,我们平时最常用的有四个属性:
- NSURLRequestUseProtocolCachePolicy: 对特定的 URL 请求使用网络协议中实现的缓存逻辑。这是默认的策略。
- NSURLRequestReloadIgnoringLocalCacheData:数据需要从原始地址加载。不使用现有缓存。
- NSURLRequestReturnCacheDataElseLoad:无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么从原始地址加载数据
- NSURLRequestReturnCacheDataDontLoad:无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么放弃从原始地址加载数据,请求视为失败(即:“离线”模式)。
// 设置默认的URLCache 大小为内存中20M 硬盘150M
+ (NSURLCache *)defaultURLCache {
return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
diskCapacity:150 * 1024 * 1024
diskPath:@"com.alamofire.imagedownloader"];
}
--
// 返回默认的NSURLSessionConfiguration
+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
//TODO set the default HTTP headers
// 接受Cookies
configuration.HTTPShouldSetCookies = YES;
// 由于并不是所有的服务器都支持管线化,所以默认的这个属性设置为NO
configuration.HTTPShouldUsePipelining = NO;
// 设置默认的缓存策略为NSURLRequestUseProtocolCachePolicy
configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
// 允许使用蜂窝网络访问
configuration.allowsCellularAccess = YES;
// 设置超时时间为60妙
configuration.timeoutIntervalForRequest = 60.0;
// 设置URLCache 策略
configuration.URLCache = [AFImageDownloader defaultURLCache];
return configuration;
}
--
// 初始化方法
- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager
downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
maximumActiveDownloads:(NSInteger)maximumActiveDownloads
imageCache:(id )imageCache {
if (self = [super init]) {
// 直接拿到方法中的参数进行赋值
self.sessionManager = sessionManager;
self.downloadPrioritizaton = downloadPrioritization;
self.maximumActiveDownloads = maximumActiveDownloads;
self.imageCache = imageCache;
// 初始化自身的一些属性
self.queuedMergedTasks = [[NSMutableArray alloc] init];
self.mergedTasks = [[NSMutableDictionary alloc] init];
self.activeRequestCount = 0;
// 初始化synchronizationQueue 这个队列是串行的
NSString *name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.synchronizationqueue-%@", [[NSUUID UUID] UUIDString]];
self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);
// 初始化responseQueue 这个队列是并行的
name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]];
self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
--
// 重写init方法
- (instancetype)init {
// 采用默认配置
NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration];
// 根据defaultConfiguration初始化一个AFHTTPSessionManager对象
AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:defaultConfiguration];
// 设置sessionManager的响应序列化为AFImageResponseSerializer(Image)
sessionManager.responseSerializer = [AFImageResponseSerializer serializer];
// 调用初始化方法
return [self initWithSessionManager:sessionManager
downloadPrioritization:AFImageDownloadPrioritizationFIFO
maximumActiveDownloads:4
imageCache:[[AFAutoPurgingImageCache alloc] init]];
}
--
// 单例
+ (instancetype)defaultInstance {
static AFImageDownloader *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
好了,通过上边3个初始化方法,我们能够简单的创建出AFImageDownloader对象了,接下来我们要做的事就是实现AFImageDownloader中下载图片的方法了。注意:我们所下载的图片对象最终被封装成AFImageDownloadReceipt。
由于下边的这个方法是核心方法,所以我采用了橙色字体来进行描述,我们将会把一个方法进行详细的剖析。
要进行剖析的方法名称,为了后边的描述,我们姑且称这个方法为 核心方法
该方法返回的是AFImageDownloadReceipt,我们看AFImageDownloadReceipt的初始化方法中都需要什么:
- (instancetype)initWithReceiptID:(NSUUID *)receiptID task:(NSURLSessionDataTask *)task
可以看出需要两个参数:1.NSURLSessionDataTask 2.receiptID 通过观察上边 核心方法 的参数,receiptID已经有了,NSURLSessionDataTask肯定是由request来生成的。
因此我们先初步写了下边的代码:
NSURLSessionDataTask *task = nil;
// 由于request不一定有效,所以可能返回nil
if (task) {
return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
return nil;
}
简单点说,核心方法 最核心的作用就是创建NSURLSessionDataTask,而我们规定,创建是在dispatch_sync中使用self.synchronizationQueue队列创建。我们又添加了如下代码:
NSURLSessionDataTask *task = nil;
/* 新增 */
dispatch_sync(self.synchronizationQueue, ^{
});
// 由于request不一定有效,所以可能返回nil
if (task) {
return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
return nil;
}
接下来,我们的代码基本就在这个block中了。我们知道我们使用AFImageDownloadReceipt中的receiptID来证明它的身份,那么同样,我们用request.URL.absoluteString来证明一个task的身份。所以我们就要拿到这个身份,并且验证它是否有效
NSURLSessionDataTask *task = nil;
dispatch_sync(self.synchronizationQueue, ^{
/* 新增 */
NSString *URLIdentifier = request.URL.absoluteString;
if (URLIdentifier == nil) {
if (failure) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
dispatch_async(dispatch_get_main_queue(), ^{
failure(request, nil, error);
});
}
return;
}
});
// 由于request不一定有效,所以可能返回nil
if (task) {
return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
return nil;
}
好了,到这里我们已经有了URLIdentifier。接下来就要处理这个问题。如果我调用了多次 核心方法 ,但是URLIdentifier都一样,也就是说我请求了同一个图片,为了性能,我们的处理思想应该是,共用同一资源但处理各自的响应结果事件。
举个例子,比如说我有 图片1 , 用它我来做3件事,(doSomething1, doSomething2, doSomething3),我发了3个请求,但是我要的结果应该拿到 图片1 ,同时能够单独调用doSomething1, doSomething2, doSomething3。
这就用带了我们上边介绍的 AFImageDownloaderMergedTask 任务合并对象了。我们用它来处理上边说的问题。在 AFImageDownloader 中的self.mergedTasks字典中放着所有的合并的任务。key就是URLIdentifier。
我们需要一个 AFImageDownloaderMergedTask ,按照正常的思路是我们先在self.mergedTasks中取,取出后把事件绑定到AFImageDownloaderMergedTask中,然后在取出它的task。别忘了我们只要得到task就行了。
__block NSURLSessionDataTask *task = nil;
dispatch_sync(self.synchronizationQueue, ^{
NSString *URLIdentifier = request.URL.absoluteString;
if (URLIdentifier == nil) {
if (failure) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
dispatch_async(dispatch_get_main_queue(), ^{
failure(request, nil, error);
});
}
return;
}
/* 新增 */
AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
if (existingMergedTask != nil) {
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure];
[existingMergedTask addResponseHandler:handler];
task = existingMergedTask.task;
return;
}
});
// 由于request不一定有效,所以可能返回nil
if (task) {
return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
return nil;
}
在这里说一下return,在这个方法中的return不是结束 核心方法 ,而是跳出dispatch_sync这个线程。那么如果在self.mergedTasks中没有这个task,我们首先应该想到的是在内存中去看看image是否存在,如果存在,我们只需要调用success就行了。
__block NSURLSessionDataTask *task = nil;
dispatch_sync(self.synchronizationQueue, ^{
NSString *URLIdentifier = request.URL.absoluteString;
if (URLIdentifier == nil) {
if (failure) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
dispatch_async(dispatch_get_main_queue(), ^{
failure(request, nil, error);
});
}
return;
}
AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
if (existingMergedTask != nil) {
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure];
[existingMergedTask addResponseHandler:handler];
task = existingMergedTask.task;
return;
}
/* 新增 */
switch (request.cachePolicy) {
case NSURLRequestUseProtocolCachePolicy:
case NSURLRequestReturnCacheDataElseLoad:
case NSURLRequestReturnCacheDataDontLoad: {
UIImage *cachedImage = [self.imageCache imageforRequest:request withAdditionalIdentifier:nil];
if (cachedImage != nil) {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^{
success(request, nil, cachedImage);
});
}
return;
}
break;
}
default:
break;
}
});
// 由于request不一定有效,所以可能返回nil
if (task) {
return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
return nil;
}
接下来相对来说就有点复杂了。我决定先写成伪代码比较好理解。我们必须创建一个task。创建很容易,来看我们创建后的代码。
__block NSURLSessionDataTask *task = nil;
dispatch_sync(self.synchronizationQueue, ^{
NSString *URLIdentifier = request.URL.absoluteString;
if (URLIdentifier == nil) {
if (failure) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
dispatch_async(dispatch_get_main_queue(), ^{
failure(request, nil, error);
});
}
return;
}
AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
if (existingMergedTask != nil) {
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure];
[existingMergedTask addResponseHandler:handler];
task = existingMergedTask.task;
return;
}
switch (request.cachePolicy) {
case NSURLRequestUseProtocolCachePolicy:
case NSURLRequestReturnCacheDataElseLoad:
case NSURLRequestReturnCacheDataDontLoad: {
UIImage *cachedImage = [self.imageCache imageforRequest:request withAdditionalIdentifier:nil];
if (cachedImage != nil) {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^{
success(request, nil, cachedImage);
});
}
return;
}
break;
}
default:
break;
}
/* 新增 */
NSURLSessionDataTask *createdTask;
// 创建task
createdTask = [self.sessionManager
dataTaskWithRequest:request
completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
// 这里先不做处理
}];
});
// 由于request不一定有效,所以可能返回nil
if (task) {
return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
return nil;
}
按理说,到了这里我们已经有了task,但别忘了,我们为了防止重复的请求专门封装了AFImageDownloaderMergedTask这个类。我们还必须用这个task来包装AFImageDownloaderMergedTask,并且添加到 AFImageDownloader 的self.mergedTasks字典中。
__block NSURLSessionDataTask *task = nil;
dispatch_sync(self.synchronizationQueue, ^{
NSString *URLIdentifier = request.URL.absoluteString;
if (URLIdentifier == nil) {
if (failure) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
dispatch_async(dispatch_get_main_queue(), ^{
failure(request, nil, error);
});
}
return;
}
AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
if (existingMergedTask != nil) {
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure];
[existingMergedTask addResponseHandler:handler];
task = existingMergedTask.task;
return;
}
switch (request.cachePolicy) {
case NSURLRequestUseProtocolCachePolicy:
case NSURLRequestReturnCacheDataElseLoad:
case NSURLRequestReturnCacheDataDontLoad: {
UIImage *cachedImage = [self.imageCache imageforRequest:request withAdditionalIdentifier:nil];
if (cachedImage != nil) {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^{
success(request, nil, cachedImage);
});
}
return;
}
break;
}
default:
break;
}
NSUUID *mergedTaskIdentifier = [NSUUID UUID];
NSURLSessionDataTask *createdTask;
// 创建task
createdTask = [self.sessionManager
dataTaskWithRequest:request
completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
// 这里先不做处理
}];
/* 新增 */
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
success:success
failure:failure];
AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
initWithURLIdentifier:URLIdentifier
identifier:mergedTaskIdentifier
task:createdTask];
[mergedTask addResponseHandler:handler];
self.mergedTasks[URLIdentifier] = mergedTask;
});
// 由于request不一定有效,所以可能返回nil
if (task) {
return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
return nil;
}
到现在为止,我们需要的东西基本上都已经齐全了。我们要做的就是开启task了,我们先来看看伪代码:
if (当前请求数小于支持的最大请求数) {
发起请求
}else {
按照我们设置的请求优先级对task进行排序
}
我们继续,‘当前请求数小于支持的最大请求数’ 这个伪代码对应下边的代码:
- (BOOL)isActiveRequestCountBelowMaximumLimit {
return self.activeRequestCount < self.maximumActiveDownloads;
}
'发起请求' 这个伪代码对应下边的代码:
- (void)startMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
[mergedTask.task resume];
++self.activeRequestCount;
}
‘按照我们设置的请求优先级对task进行排序’ 我们规定,当我们在数组中取task时,都是取最前边的值,这么这个伪代码对应下边的代码:
- (void)enqueueMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
switch (self.downloadPrioritizaton) {
case AFImageDownloadPrioritizationFIFO: // 先进先出,添加到数组后边
[self.queuedMergedTasks addObject:mergedTask];
break;
case AFImageDownloadPrioritizationLIFO: // 后进先出, 添加到数组前边
[self.queuedMergedTasks insertObject:mergedTask atIndex:0];
break;
}
}
替换掉伪代码后我们得到的代码如下:
__block NSURLSessionDataTask *task = nil;
dispatch_sync(self.synchronizationQueue, ^{
NSString *URLIdentifier = request.URL.absoluteString;
if (URLIdentifier == nil) {
if (failure) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
dispatch_async(dispatch_get_main_queue(), ^{
failure(request, nil, error);
});
}
return;
}
AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
if (existingMergedTask != nil) {
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure];
[existingMergedTask addResponseHandler:handler];
task = existingMergedTask.task;
return;
}
switch (request.cachePolicy) {
case NSURLRequestUseProtocolCachePolicy:
case NSURLRequestReturnCacheDataElseLoad:
case NSURLRequestReturnCacheDataDontLoad: {
UIImage *cachedImage = [self.imageCache imageforRequest:request withAdditionalIdentifier:nil];
if (cachedImage != nil) {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^{
success(request, nil, cachedImage);
});
}
return;
}
break;
}
default:
break;
}
NSUUID *mergedTaskIdentifier = [NSUUID UUID];
NSURLSessionDataTask *createdTask;
// 创建task
createdTask = [self.sessionManager
dataTaskWithRequest:request
completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
// 这里先不做处理
}];
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
success:success
failure:failure];
AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
initWithURLIdentifier:URLIdentifier
identifier:mergedTaskIdentifier
task:createdTask];
[mergedTask addResponseHandler:handler];
self.mergedTasks[URLIdentifier] = mergedTask;
/* 新增 */
if ([self isActiveRequestCountBelowMaximumLimit]) {
[self startMergedTask:mergedTask];
} else {
[self enqueueMergedTask:mergedTask];
}
});
// 由于request不一定有效,所以可能返回nil
if (task) {
return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
return nil;
}
ok,大功告成。我们已经把 核心方法 一步一步的实现了。那么最后一点,我们就要专门处理当每个请求完成后的一些事件了。同样我们还是使用伪代码来解释。这些代码放在创建task的那个回调中。
// 创建task
createdTask = [self.sessionManager
dataTaskWithRequest:request
completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
// 由于我们把所有的事件处理都绑定在AFImageDownloaderMergedTask中。所以我们第一步要先获取这个对象
1. 获取AFImageDownloaderMergedTask
// 确保这个mergedTask跟这个createdTask是同一个
2. 如果mergedTask跟这个createdTask是同一个
// 虽然self.mergedTasks中也能取到mergedTask,但我们最终用到的数据还是来自于数组中取出这个mergedTask,取出后要删除掉
3.在mergedTask数组中取出这个mergedTask后删除数组中的这个mergedTask
if (错误) {
4. 遍历mergedTask中的绑定事件中的错误block
}else {
5. 把图片添加进缓存
6. 遍历mergedTask中的绑定事件中的成功block
}
7.当前的请求数减1
8.开启下一个请求
}];
我们逐一翻译伪代码:
首先 1. 获取AFImageDownloaderMergedTask
对应下边的代码:
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
2. 如果mergedTask跟这个createdTask是同一个
对应下边的代码:
if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {}
3.在mergedTask数组中取出这个mergedTask后删除数组中的这个mergedTask
对应下边的代码:
- (AFImageDownloaderMergedTask*)safelyRemoveMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
__block AFImageDownloaderMergedTask *mergedTask = nil;
dispatch_sync(self.synchronizationQueue, ^{
mergedTask = [self removeMergedTaskWithURLIdentifier:URLIdentifier];
});
return mergedTask;
}
//This method should only be called from safely within the synchronizationQueue
- (AFImageDownloaderMergedTask *)removeMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
[self.mergedTasks removeObjectForKey:URLIdentifier];
return mergedTask;
}
4. 遍历mergedTask中的绑定事件中的错误block
对应下边的代码:
for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(request, (NSHTTPURLResponse*)response, error);
});
}
}
5. 把图片添加进缓存
对应下边的代码:
// 保存image到缓存中
[strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
6. 遍历mergedTask中的绑定事件中的成功block
对应下边的代码:
for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
if (handler.successBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.successBlock(request, (NSHTTPURLResponse*)response, responseObject);
});
}
}
7.当前的请求数减1
对应下边的代码:
- (void)safelyDecrementActiveTaskCount {
dispatch_sync(self.synchronizationQueue, ^{
if (self.activeRequestCount > 0) {
self.activeRequestCount -= 1;
}
});
}
开启下一个请求
对应下边的代码:
- (void)safelyStartNextTaskIfNecessary {
dispatch_sync(self.synchronizationQueue, ^{
if ([self isActiveRequestCountBelowMaximumLimit]) {
while (self.queuedMergedTasks.count > 0) {
AFImageDownloaderMergedTask *mergedTask = [self dequeueMergedTask];
if (mergedTask.task.state == NSURLSessionTaskStateSuspended) {
[self startMergedTask:mergedTask];
break;
}
}
}
});
}
看上去这个 核心方法 占用了这么多的篇幅,其实在实际编码中确实很难考虑的这么详细。我们也是按照目的倒推来进行解读的。那么,最终我们来看看完成的代码:
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
withReceiptID:(nonnull NSUUID *)receiptID
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure {
__block NSURLSessionDataTask *task = nil;
dispatch_sync(self.synchronizationQueue, ^{
NSString *URLIdentifier = request.URL.absoluteString;
if (URLIdentifier == nil) {
if (failure) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
dispatch_async(dispatch_get_main_queue(), ^{
failure(request, nil, error);
});
}
return;
}
// 1) Append the success and failure blocks to a pre-existing request if it already exists
AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
if (existingMergedTask != nil) {
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure];
[existingMergedTask addResponseHandler:handler];
task = existingMergedTask.task;
return;
}
// 2) Attempt to load the image from the image cache if the cache policy allows it
switch (request.cachePolicy) {
case NSURLRequestUseProtocolCachePolicy:
case NSURLRequestReturnCacheDataElseLoad:
case NSURLRequestReturnCacheDataDontLoad: {
UIImage *cachedImage = [self.imageCache imageforRequest:request withAdditionalIdentifier:nil];
if (cachedImage != nil) {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^{
success(request, nil, cachedImage);
});
}
return;
}
break;
}
default:
break;
}
// 3) Create the request and set up authentication, validation and response serialization
NSUUID *mergedTaskIdentifier = [NSUUID UUID];
NSURLSessionDataTask *createdTask;
__weak __typeof__(self) weakSelf = self;
// 创建task
createdTask = [self.sessionManager
dataTaskWithRequest:request
completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
// 得到响应之后,这里的responseObject为image
dispatch_async(self.responseQueue, ^{
__strong __typeof__(weakSelf) strongSelf = weakSelf;
// 这里为什么又再次验证self.mergedTasks是不是存在这个task呢?这是因为在我们新建了createdTask之后,就把createdTask添加到了self.mergedTasks中,因此,一般情况下调用self.mergedTasks[URLIdentifier]能够得到mergedTask
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier];
if (error) {
for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(request, (NSHTTPURLResponse*)response, error);
});
}
}
} else {
// 保存image到缓存中
[strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
if (handler.successBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.successBlock(request, (NSHTTPURLResponse*)response, responseObject);
});
}
}
}
}
[strongSelf safelyDecrementActiveTaskCount];
[strongSelf safelyStartNextTaskIfNecessary];
});
}];
// 4) Store the response handler for use when the request completes
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
success:success
failure:failure];
AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
initWithURLIdentifier:URLIdentifier
identifier:mergedTaskIdentifier
task:createdTask];
[mergedTask addResponseHandler:handler];
self.mergedTasks[URLIdentifier] = mergedTask;
// 5) Either start the request or enqueue it depending on the current active request count
if ([self isActiveRequestCountBelowMaximumLimit]) {
[self startMergedTask:mergedTask];
} else {
[self enqueueMergedTask:mergedTask];
}
task = mergedTask.task;
});
if (task) {
return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
return nil;
}
}
接下来,我们只剩下一个方法了,就是取消一个请求。这个我直接写到代码中了。
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
dispatch_sync(self.synchronizationQueue, ^{
// 取出标识
NSString *URLIdentifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString;
// 取出mergedTask
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
// 获取凭证在responseHandlers中的位置
NSUInteger index = [mergedTask.responseHandlers indexOfObjectPassingTest:^BOOL(AFImageDownloaderResponseHandler * _Nonnull handler, __unused NSUInteger idx, __unused BOOL * _Nonnull stop) {
return handler.uuid == imageDownloadReceipt.receiptID;
}];
if (index != NSNotFound) {
AFImageDownloaderResponseHandler *handler = mergedTask.responseHandlers[index];
// 移除事件处理
[mergedTask removeResponseHandler:handler];
// 给出错误
NSString *failureReason = [NSString stringWithFormat:@"ImageDownloader cancelled URL request: %@",imageDownloadReceipt.task.originalRequest.URL.absoluteString];
NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey:failureReason};
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo];
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(imageDownloadReceipt.task.originalRequest, nil, error);
});
}
}
// 只有当没有Handler且Suspended,要取消任务然后移除掉mergedTask
if (mergedTask.responseHandlers.count == 0 && mergedTask.task.state == NSURLSessionTaskStateSuspended) {
[mergedTask.task cancel];
[self removeMergedTaskWithURLIdentifier:URLIdentifier];
}
});
}
总结
之前一直不太清楚图片缓存的原理是什么?现在大概了解了。通过对本篇中的代码的研究,为我们自己实现一些图片或者其他方面的功能提供了参考。
参考链接
- IOS之NSArray 中调用的方法详解(2)
- NSURLCache
推荐阅读
AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager
AFNetworking 3.0 源码解读(二)之 AFSecurityPolicy
AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization
AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization
AFNetworking 3.0 源码解读(五)之 AFURLSessionManager
AFNetworking 3.0 源码解读(六)之 AFHTTPSessionManager
AFNetworking 3.0 源码解读(七)之 AFAutoPurgingImageCache