AFImageDownloader 源码学习

871 阅读7分钟
原文链接: www.jianshu.com

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;
    • 如何取消下载请求:
      -(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;
  • 取消下载的流程

    • 使用receipt来取消下载,将取消下载操作封装成闭包放入synchronizationQueue队列中,dispatch_sync(self.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;
          }];
    • 如果找到了对应的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中移除。
  • 一系列的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;
                  }
              }
          }
      });
      }
  • 避免死锁的操作

    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打头的方法。
  • 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;
    }