iOS AFN AFImageDownloader 源码分析
AFN AFImageDownloader 源码分析
源码也是很简单,再次来学人家的思想,其实读源码最终就是学习人家的设计和思想,到最后你会变成一个特别喜欢读源码的人
AFImageDownloaderResponseHandler
我们正常在设计接口的时候,都会留给别人一个成功或者失败的回调,但有没有想过一个问题,比如我一个操作调用多次,但这个操作是重复的,但我只关心成功或者失败的回调,那么需要怎么办?或者换句话说,我们平时调用代理的时候,设置代理,都是设置单一代理,那么这个代理,最终只会被回到一次到一个地方,我们也是会有需求,设置多个代理,那么怎么做呢?
这里作者将一个接口中的成功或者失败额回调,抽象到一个具体的类里面去保存,而之后将这个类保存到一个字典中,每次的key不同,就能实现,多次回调block或者代理的需求了
AFImageDownloaderMergedTask
这个类就是对下载图片task的一个封装
@interface AFImageDownloaderMergedTask : NSObject
@property (nonatomic, strong) NSString *URLIdentifier;
@property (nonatomic, strong) NSUUID *identifier;
@property (nonatomic, strong) NSURLSessionDataTask *task;
@property (nonatomic, strong) NSMutableArray <AFImageDownloaderResponseHandler*> *responseHandlers;
@end
可以看到他保存着请求的url,id标识,任务task,和上面所说的回调数组,也就是说一个task可以有多个response回调block
AFImageDownloader
+ (NSURLCache *)defaultURLCache {
// 这篇文章不错:https://www.cnblogs.com/madpanda/p/4700741.html
NSUInteger memoryCapacity = 20 * 1024 * 1024; // 20MB
NSUInteger diskCapacity = 150 * 1024 * 1024; // 150MB
NSURL *cacheURL = [[[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:YES
error:nil]
URLByAppendingPathComponent:@"com.alamofire.imagedownloader"];
#if TARGET_OS_MACCATALYST
return [[NSURLCache alloc] initWithMemoryCapacity:memoryCapacity
diskCapacity:diskCapacity
directoryURL:cacheURL];
#else
return [[NSURLCache alloc] initWithMemoryCapacity:memoryCapacity
diskCapacity:diskCapacity
diskPath:[cacheURL path]];
#endif
}
+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
//TODO set the default HTTP headers
configuration.HTTPShouldSetCookies = YES;
configuration.HTTPShouldUsePipelining = NO;
configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
configuration.allowsCellularAccess = YES;
configuration.timeoutIntervalForRequest = 60.0;
configuration.URLCache = [AFImageDownloader defaultURLCache];
return configuration;
}
这里会造一个NSURLCache的类,用于缓存,主要是配置个系统的NSURLSessionConfiguration这个类,是依据这个属性的requestCachePolicy,也就是说缓存策略,是否从缓存中读取,如果这个策略中选择了从缓存中读取,那么我们下载完图片之后会存放在缓存中,下次同一个请求,就可以不请求了,直接从缓存中读取图片就可以了,后面会讲到,,这里可以看到,申请内存缓存20M,磁盘缓存250M,
- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager
downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
maximumActiveDownloads:(NSInteger)maximumActiveDownloads
imageCache:(id <AFImageRequestCache>)imageCache {
if (self = [super init]) {
self.sessionManager = sessionManager;
// 下载策略,先进先出,还是先进后出
self.downloadPrioritization = downloadPrioritization;
// 允许最大下载个数
self.maximumActiveDownloads = maximumActiveDownloads;
// 图片缓存实现,AFN提供了缓存策略,也可以自己实现
self.imageCache = imageCache;
// 队列中的任务
self.queuedMergedTasks = [[NSMutableArray alloc] init];
self.mergedTasks = [[NSMutableDictionary alloc] init];
self.activeRequestCount = 0;
NSString *name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.synchronizationqueue-%@", [[NSUUID UUID] UUIDString]];
// 事件触发同步队列
self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);
// 响应队列
name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]];
self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
- (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, ^{
// url 为唯一标识符
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
// url 为存储任务的唯一标识
AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
if (existingMergedTask != nil) {
// 如果找到了task,说明这个url已经请求过了,已经有存在的任务了
// 然后根据传进来的receiptID创建回调封装类
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: {
// 如果策略中包含从缓存中读取,那么就调用我们之前的 imageCache 工厂,取出我们已经缓存的图片,就不用每次都请求加载了
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;
// 开始异步请求
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;
// 从我们的缓存中,根据url取出来我们保存的已经创建好的task封装类
AFImageDownloaderMergedTask *mergedTask = [strongSelf safelyGetMergedTask:URLIdentifier];
if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
// 然后移除
mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier];
if (error) {
// 如果发生失败,那么从我们的task封装类里面,取出回调封装类,所有的,然后调用这个封装类里面的失败的回调,一次回调多次
for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(request, (NSHTTPURLResponse *)response, error);
});
}
}
} else {
// 成功了之后,选择是否将图片缓存,默认是允许缓存
if ([strongSelf.imageCache shouldCacheImage:responseObject forRequest:request withAdditionalIdentifier:nil]) {
// 缓存策略为,key为url拼接AdditionalIdentifier这个字符串,因为这里传的nil,所以key就为url,value就为下载的这个图片数据
[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);
});
}
}
}
}
// 将当前请求的数据-1
[strongSelf safelyDecrementActiveTaskCount];
// 拿出下一个请求,进行请求
[strongSelf safelyStartNextTaskIfNecessary];
});
}];
// 4) Store the response handler for use when the request completes
// 根据穿件来的 receiptID 创建封装回调类,将成功和失败回调封装进去
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
success:success
failure:failure];
// 创建task封装类,将url和taskID还有当前task传进去保存
AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
initWithURLIdentifier:URLIdentifier
identifier:mergedTaskIdentifier
task:createdTask];
// 当前task保存当前封装的会叫类
[mergedTask addResponseHandler:handler];
// mergedTasks 缓存中保存当前task,注意是以 url 为唯一标识
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;
}
}
这个方法比较核心,如果发起来同一个请求,会直接返回已经发起额这个请求,然后将回调保存到请求里面,所以就会出现回调多次的情况,然后如果我们的缓存策略允许从缓存读取,也会优先从我们的缓存中读取,最后返回一个 AFImageDownloadReceipt 对象,这个对象是用来取消任务的,
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
dispatch_sync(self.synchronizationQueue, ^{
// 取出需要取消的tash 的url
NSString *URLIdentifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString;
// 从缓存中找出这个task
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
// 从这个task封装类的responseHandlers里面,这个responseHandlers里面存放着所有的回调类,找到和需要需要的这个receiptID一样的那个回调
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);
});
}
}
// 因为当前取消的是单个任务,因为有可能会一个任务请求多次但是ID不一样,如果里面还有别的回调,那么就继续下去,如果他的回调都删除光了,其实就是说明,当前任务需要取消了,那么就将任务取消,因为没有接受回调了
if (mergedTask.responseHandlers.count == 0) {
[mergedTask.task cancel];
// 任务取消之后,也从缓存中消失
[self removeMergedTaskWithURLIdentifier:URLIdentifier];
}
});
}
下面就是一些同步串行的增删取操作
- (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;
}
- (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;
}
}
}
});
}
- (void)startMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
[mergedTask.task resume];
++self.activeRequestCount;
}
- (void)enqueueMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
switch (self.downloadPrioritization) {
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;
}
- (BOOL)isActiveRequestCountBelowMaximumLimit {
return self.activeRequestCount < self.maximumActiveDownloads;
}
- (AFImageDownloaderMergedTask *)safelyGetMergedTask:(NSString *)URLIdentifier {
__block AFImageDownloaderMergedTask *mergedTask;
// 串行同步读取
dispatch_sync(self.synchronizationQueue, ^(){
mergedTask = self.mergedTasks[URLIdentifier];
});
return mergedTask;
}