AFImageDownloader:
可以设置最大并发量:@property (nonatomic, assign) NSInteger maximumActiveDownloads;
,可以设置下载策略:@property (nonatomic, assign) AFImageDownloadPrioritization downloadPrioritizaton;
,FIFO先进先出,LIFO后进先出。
typedef NS_ENUM(NSInteger, AFImageDownloadPrioritization) {
AFImageDownloadPrioritizationFIFO,
AFImageDownloadPrioritizationLIFO
};
下载队列为:@property (nonatomic, strong) NSMutableArray *queuedMergedTasks;
先了解AFImageDownloadReceipt:
当用AFImageDownloader对象请求下载NSURLRequest时,会返回一个AFImageDownloadReceipt
实例(后面被称为“凭证”),需要通过这个凭证来取消下载请求,该类型封装了@property (nonatomic, strong) NSURLSessionDataTask *task;
,所以可以通过这个凭证来监听task的进度。
想想:为什么请求下载时返回的是AFImageDownloadReceipt实例,而不是NSURLSessionDataTask对象?
因为当调用AFImageDownloader对象,对同一个url反复请求下载时,此时AFImageDownloader只会生成一个task(下载任务),但是这个task可以拥有多个responseHandler(AFImageDownloaderResponseHandler实例,封装了下载完成后的回调方法)对象,也就是说我们每次发起下载请求并不一定生成task,可能会生成receipt(之所以是可能,是因为该url下面的图片可能已经被缓存下来,当发起下载请求时会直接返回该图片)。所以外部发起下载请求时并不关心你是否创建了task,只关心这个请求的结果,也就是responseHandler。receipt封装了属性@property (nonatomic, strong) NSUUID *receiptID;
来标记这个下载请求对应的responseHandler。取消下载请求时可以通过receiptID来移除responseHandler即可。
AFImageDownloadReceipt的定义如下:
@interface AFImageDownloadReceipt : NSObject
@property (nonatomic, strong) NSURLSessionDataTask *task;
@property (nonatomic, strong) NSUUID *receiptID;
@end
了解下AFImageDownloaderResponseHandler:
定义如下:
@interface AFImageDownloaderResponseHandler : NSObject
@property (nonatomic, strong) NSUUID *uuid;
@property (nonatomic, copy) void (^successBlock)(NSURLRequest*, NSHTTPURLResponse*, UIImage*);
@property (nonatomic, copy) void (^failureBlock)(NSURLRequest*, NSHTTPURLResponse*, NSError*);
@end
@implementation AFImageDownloaderResponseHandler
- (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]];
}
以上代码很容易理解,successBlock和failureBlock的回调方法前两个参数是一样的,NSURLRequest和NSHTTPURLResponse,successBlock的最后一个参数返回的是UIImage,failureBlock最后一个参数返回的是NSError。uuid与receipt的receiptID是一致的。
再来了解下AFImageDownloaderMergedTask:
从名字就能看出这是个合并下载任务的类型,合并什么样的下载任务呢?就是前面所提到的,相同的url下载请求,只生成了一个task,但是可以有多个response,定义如下:
@interface AFImageDownloaderMergedTask : NSObject
@property (nonatomic, strong) NSString *URLIdentifier;
@property (nonatomic, strong) NSUUID *identifier;
@property (nonatomic, strong) NSURLSessionDataTask *task;
@property (nonatomic, strong) NSMutableArray *responseHandlers;
@end
URLIdentifier属性对应的是下载url对象(如http://test/test.jpg)
identifier属性:使用图片异步下载的都应该知道,当请求下载url后,请求可能会被取消然后重新发起(此时虽然url一样,但是identifier不一样了),那当异步请求返回结果的时候怎样确保当前返回的结果是我想要的?不是之前已经被取消的请求结果?这时候就要通过identifier属性来判断了。每次对url生成task,都会当场生成一个identifier,异步请求返回处理的闭包会捕获这个identifier,同时这个identifier也会保存进AFImageDownloaderMergedTask对象中。只要判断AFImageDownloaderMergedTask对象中的identifier与闭包捕获的identifier是否一致,就可能判断这个返回结果是否要生效了。
AFImageDownloaderMergedTask实现如下:
@implementation AFImageDownloaderMergedTask
- (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];
}
@end
可以看出,只有创建task时才会创建AFImageDownloaderMergedTask。提供了为task添加和移除responseHandler的方法。
可以只看AFImageDownloader了:
-
对外接口定义:
-
@property (nonatomic, strong, nullable) id imageCache;
,参考文章,默认为:AFAutoPurgingImageCache类型的对象。 -
@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;
,用来下载图片的sessionManager,默认情况下,responseSerializer为AFImageResponseSerializer。AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:defaultConfiguration]; sessionManager.responseSerializer = [AFImageResponseSerializer serializer];
@property (nonatomic, assign) AFImageDownloadPrioritization downloadPrioritizaton;
+ (instancetype)defaultInstance;
- 如何发起下载请求:
- 这个不需要给出凭证receiptID,里面会使用[NSUUID UUID]生成receiptID
-(nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;
- 这个需要给出receiptID
-(nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request withReceiptID:(NSUUID *)receiptID success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;
- 这个不需要给出凭证receiptID,里面会使用[NSUUID UUID]生成receiptID
- 如何取消下载请求:
-(void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt;
-
-
匿名类别中的私有属性定义:
- 可同时被激活的最大请求数量:
@property (nonatomic, assign) NSInteger maximumActiveDownloads;
- 已经被激活的请求数量:
@property (nonatomic, assign) NSInteger activeRequestCount;
请求队列,用来控制FIFO或者LIFO:@property (nonatomic, strong) NSMutableArray *queuedMergedTasks;
- 字典:
@property (nonatomic, strong) NSMutableDictionary *mergedTasks;
,key为url,value为AFImageDownloaderMergedTask。 - synchronizationQueue串行队列,用来执行写操作(queuedMergedTasks,activeRequestCount,mergedTasks的修改):
NSString *name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.synchronizationqueue-%@", [[NSUUID UUID] UUIDString]]; self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);
- responseQueue队列,用来回调responseHandler的队列,并行,无需等待:
name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]]; self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
- 可同时被激活的最大请求数量:
-
下载流程:
- 定义属性
__block NSURLSessionDataTask *task = nil;
,该值在闭包内可以被修改。 - 将下载操作封装成闭包放入synchronizationQueue队列中 -
dispatch_sync(self.synchronizationQueue, ^{//下载操作...})
。 - 使用task和receiptID创建receipt,并返回
if (task) { return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task]; } else { return nil; }
- 定义属性
-
synchronizationQueue中的下载操作
- 首先判断给定的NSURLRequest是否有完整路径,没有则在主线程调用failure并退出闭包操作:
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; }
- 在mergedTasks中通过key:URLIdentifier来判断是否有存在的AFImageDownloaderMergedTask,如果有则为新的请求添加responseHandler,将task赋值为existingMergedTask.task,然后退出闭包:
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; }
- 判断请求是否允许访问缓存里的值,如果该请求可以使用缓存,并且缓存中有该请求对应的图片,则在主线程执行success,把图片传入进去,并退出闭包:
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; }
-
在没有发起过该url请求,并没有缓存的情况下,生成新的task:
NSURLSessionDataTask *createdTask; NSUUID *mergedTaskIdentifier = [NSUUID UUID]; __weak __typeof__(self) weakSelf = self; createdTask = [self.sessionManager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { dispatch_async(self.responseQueue, ^{ __strong __typeof__(weakSelf) strongSelf = weakSelf; //在responseQueue中处理请求结果 //捕获了mergedTaskIdentifier和URLIdentifier //通过URLIdentifier在strongSelf.mergedTasks中取得AFImageDownloaderMergedTask //如果获取失败,或者获取成功,但是mergedTask.identifier与闭包捕获的mergedTaskIdentifier不一致,则证明这是不需要被处理的请求结果。 AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier]; if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) { //移除下载task mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier]; if (error) { for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) { if (handler.failureBlock) { //在主线程执行failure回调 dispatch_async(dispatch_get_main_queue(), ^{ handler.failureBlock(request, (NSHTTPURLResponse*)response, error); }); } } } else { //只要成功,就将图片加入缓存 [strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil]; for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) { if (handler.successBlock) { //在主线程执行success回调 dispatch_async(dispatch_get_main_queue(), ^{ handler.successBlock(request, (NSHTTPURLResponse*)response, responseObject); }); } } } } //已经被激活的请求数量减1 //开始下一个下载任务 [strongSelf safelyDecrementActiveTaskCount]; [strongSelf safelyStartNextTaskIfNecessary]; }); }];
- 生成新的responseHandler和mergedTask,并且把responseHandler添加进mergedTask,将mergedTask为value,URLIdentifier为key加入self.mergedTasks中。
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;
- 开始执行mergedTask,或者将mergedTask根据downloadPrioritizaton插入queuedMergedTasks的头部或尾部:
if ([self isActiveRequestCountBelowMaximumLimit]) { [self startMergedTask:mergedTask]; } else { [self enqueueMergedTask:mergedTask]; }
- 将__block变量task赋值
task = mergedTask.task;
- 首先判断给定的NSURLRequest是否有完整路径,没有则在主线程调用failure并退出闭包操作:
-
取消下载的流程
- 使用receipt来取消下载,将取消下载操作封装成闭包放入synchronizationQueue队列中,
dispatch_sync(self.synchronizationQueue, ^{//取消下载的操作...});
- 使用receipt来取消下载,将取消下载操作封装成闭包放入synchronizationQueue队列中,
-
取消下载的操作
- 通过receipt来获取responseHandler
- 通过receipt来获取URLIdentifier
NSString *URLIdentifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString;
- 通过URLIdentifier来获取mergedTask:
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
- 通过mergedTask中的responseHandlers,和AFImageDownloaderResponseHandler中的uuid来获得receipt对应的responseHandler:
NSUInteger index = [mergedTask.responseHandlers indexOfObjectPassingTest:^BOOL(AFImageDownloaderResponseHandler * _Nonnull handler, __unused NSUInteger idx, __unused BOOL * _Nonnull stop) { return handler.uuid == imageDownloadReceipt.receiptID; }];
- 通过receipt来获取URLIdentifier
- 如果找到了对应的responseHandler,则将responseHandler在mergedTask中移除,并在主线程调用responseHandler的failure回调:
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); }); } }
- 如果mergedTask中没有一个responseHandler了,并且task属于挂起状态(以为在下载完成的回调中也会将mergeTask移除),则将mergedTask从self.mergedTasks中移除。
- 通过receipt来获取responseHandler
-
一系列的safe操作,都是在队列synchronizationQueue中执行的同步操作:
- 移除mergedTask:
-(AFImageDownloaderMergedTask*)safelyRemoveMergedTaskWithURLIdentifier:(NSString *)URLIdentifier { __block AFImageDownloaderMergedTask *mergedTask = nil; dispatch_sync(self.synchronizationQueue, ^{ mergedTask = [self removeMergedTaskWithURLIdentifier:URLIdentifier]; }); return mergedTask; }
- 减少当前已激活的task数量:
-(void)safelyDecrementActiveTaskCount { dispatch_sync(self.synchronizationQueue, ^{ if (self.activeRequestCount > 0) { self.activeRequestCount -= 1; } }); }
- resume下一个task:
-(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; } } } }); }
- 移除mergedTask:
-
避免死锁的操作
dispatch_sync(self.synchronizationQueue, ^{ A dispatch_sync(self.synchronizationQueue, ^ {B}) })
以上代码会发生死锁,因为当执行到A时,要继续走下去就必须执行B,但是B说我不能执行,因为我队列里面前面的操作还没执行完(因为是串行队列),所以就呵呵哒了,大家都无法执行了。
这也是为什么dispatch_sync(dispatch_main_queue., ^{...})
会死锁的原因,因为dispatch_man_queue也是串行队列啊。- 移除mergedTask
-(AFImageDownloaderMergedTask *)removeMergedTaskWithURLIdentifier:(NSString *)URLIdentifier { AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier]; [self.mergedTasks removeObjectForKey:URLIdentifier]; return mergedTask; }
- 开始resume task
这就是为什么在-(void)startMergedTask:(AFImageDownloaderMergedTask *)mergedTask { [mergedTask.task resume]; ++self.activeRequestCount; }
dispatch_async(self.responseQueue,^{...})
里面要调用safe开头的方法。
在dispatch_sync(self.synchronizationQueue, ^{...})
里面不调用safe打头的方法。
- 移除mergedTask
-
task下载队列的管理
-(void)enqueueMergedTask:(AFImageDownloaderMergedTask *)mergedTask { switch (self.downloadPrioritizaton) { case AFImageDownloadPrioritizationFIFO: [self.queuedMergedTasks addObject:mergedTask]; break; case AFImageDownloadPrioritizationLIFO: [self.queuedMergedTasks insertObject:mergedTask atIndex:0]; break; } } -(AFImageDownloaderMergedTask *)dequeueMergedTask { AFImageDownloaderMergedTask *mergedTask = nil; mergedTask = [self.queuedMergedTasks firstObject]; [self.queuedMergedTasks removeObject:mergedTask]; return mergedTask; }