AFNetworking源码探究(二十) —— UIKit相关之AFImageDownloader图像下载(三)

版本记录

版本号 时间
V1.0 2018.03.04

前言

我们做APP发起网络请求,都离不开一个非常有用的框架AFNetworking,可以说这个框架的知名度已经超过了苹果的底层网络请求部分,很多人可能不知道苹果底层是如何发起网络请求的,但是一定知道AFNetworking,接下来几篇我们就一起详细的解析一下这个框架。感兴趣的可以看上面写的几篇。
1. AFNetworking源码探究(一) —— 基本介绍
2. AFNetworking源码探究(二) —— GET请求实现之NSURLSessionDataTask实例化(一)
3. AFNetworking源码探究(三) —— GET请求实现之任务进度设置和通知监听(一)
4. AFNetworking源码探究(四) —— GET请求实现之代理转发思想(一)
5. AFNetworking源码探究(五) —— AFURLSessionManager中NSURLSessionDelegate详细解析(一)
6. AFNetworking源码探究(六) —— AFURLSessionManager中NSURLSessionTaskDelegate详细解析(一)
7. AFNetworking源码探究(七) —— AFURLSessionManager中NSURLSessionDataDelegate详细解析(一)
8. AFNetworking源码探究(八) —— AFURLSessionManager中NSURLSessionDownloadDelegate详细解析(一)
9. AFNetworking源码探究(九) —— AFURLSessionManagerTaskDelegate中三个转发代理方法详细解析(一)
10. AFNetworking源码探究(十) —— 数据解析之数据解析架构的分析(一)
11. AFNetworking源码探究(十一) —— 数据解析之子类中协议方法的实现(二)
12. AFNetworking源码探究(十二) —— 数据解析之子类中协议方法的实现(三)
13. AFNetworking源码探究(十三) —— AFSecurityPolicy与安全认证 (一)
14. AFNetworking源码探究(十四) —— AFSecurityPolicy与安全认证 (二)
15. AFNetworking源码探究(十五) —— 请求序列化之架构分析(一)
16. AFNetworking源码探究(十六) —— 请求序列化之协议方法的实现(二)
17. AFNetworking源码探究(十七) —— _AFURLSessionTaskSwizzling实现方法交换(转载)(一)
18. AFNetworking源码探究(十八) —— UIKit相关之AFNetworkActivityIndicatorManager(一)
19. AFNetworking源码探究(十九) —— UIKit相关之几个分类(二)

回顾

上一篇主要介绍了AFNetworkActivityIndicatorManager这个与UIKit相关的类,这一篇主要介绍AFImageDownloader有关图像的下载。


AFImageDownloader文件

我们先看一下这个文件,这个文件包含两个类。分别为AFImageDownloadReceiptAFImageDownloader

1. AFImageDownloader

先看一下AFImageDownloader.h的接口

1. AFImageDownloader.h
/** The `AFImageDownloader` class is responsible for downloading images in parallel on a prioritized queue. Incoming downloads are added to the front or back of the queue depending on the download prioritization. Each downloaded image is cached in the underlying `NSURLCache` as well as the in-memory image cache. By default, any download request with a cached image equivalent in the image cache will automatically be served the cached image representation.
 */
@interface AFImageDownloader : NSObject

/**
 The image cache used to store all downloaded images in. `AFAutoPurgingImageCache` by default.
 */
// 图像缓存默认情况下用于将所有下载的图像存储在AFAutoPurgingImageCache中。
@property (nonatomic, strong, nullable) id  imageCache;

/**
 The `AFHTTPSessionManager` used to download images. By default, this is configured with an `AFImageResponseSerializer`, and a shared `NSURLCache` for all image downloads.
 */
// 用于下载图像的AFHTTPSessionManager。 默认情况下,
// 这是通过一个AFImageResponseSerializer和一个共享的NSURLCache来配置所有的图片下载。
@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;

/**
 Defines the order prioritization of incoming download requests being inserted into the queue. `AFImageDownloadPrioritizationFIFO` by default.
 */
// 定义插入队列的传入下载请求的顺序优先级。 默认情况下为AFImageDownloadPrioritizationFIFO。
@property (nonatomic, assign) AFImageDownloadPrioritization downloadPrioritizaton;

/**
 The shared default instance of `AFImageDownloader` initialized with default values.
 */
// AFImageDownloader的共享默认实例使用默认值初始化。
+ (instancetype)defaultInstance;

/**
 Creates a default `NSURLCache` with common usage parameter values.

 @returns The default `NSURLCache` instance.
 */
// 使用常用的使用参数值创建一个默认的NSURLCache
+ (NSURLCache *)defaultURLCache;

/**
 The default `NSURLSessionConfiguration` with common usage parameter values.
 */
// 默认的NSURLSessionConfiguration具有常用的使用参数值。
+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration;

/**
 Default initializer

 @return An instance of `AFImageDownloader` initialized with default values.
 */
- (instancetype)init;

/**
 Initializer with specific `URLSessionConfiguration`
 
 @param configuration The `NSURLSessionConfiguration` to be be used
 
 @return An instance of `AFImageDownloader` initialized with default values and custom `NSURLSessionConfiguration`
 */
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration;

/**
 Initializes the `AFImageDownloader` instance with the given session manager, download prioritization, maximum active download count and image cache.
// 使用给定的会话管理器、下载优先级,最大活动下载计数和图像缓存
// 来初始化AFImageDownloader实例。

 @param sessionManager The session manager to use to download images.
 @param downloadPrioritization The download prioritization of the download queue.
 @param maximumActiveDownloads  The maximum number of active downloads allowed at any given time. Recommend `4`.
 @param imageCache The image cache used to store all downloaded images in.

 @return The new `AFImageDownloader` instance.
 */
- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager
                downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
                maximumActiveDownloads:(NSInteger)maximumActiveDownloads
                            imageCache:(nullable id )imageCache;

/**
 Creates a data task using the `sessionManager` instance for the specified URL request.

 If the same data task is already in the queue or currently being downloaded, the success and failure blocks are
 appended to the already existing task. Once the task completes, all success or failure blocks attached to the
 task are executed in the order they were added.
// 使用sessionManager实例为指定的URL请求创建一个数据任务。

  如果相同的数据任务已经在队列中或当前正在下载,则成功和失败模块是
  附加到已经存在的任务。 一旦任务完成,所有成功或失败块附加到
  任务并按照它们添加的顺序执行。

 @param request The URL request.
 @param success A block to be executed when the image data task finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the response parameter will be `nil`.
 // 当图像数据任务成功完成时要执行的块。 该块没有返回值,并且有三个参数:
 // 客户端发送的请求,从服务器收到的响应以及从请求响应数据创建的图像。 
 // 如果图像是从缓存中返回的,则响应参数将为nil。
 @param failure A block object to be executed when the image data task finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred.
 // 当图像数据任务完成失败或成功完成时要执行的块对象。 
 // 该块没有返回值,并且有三个参数:客户端发送的请求,
 // 从服务器接收到的响应以及描述发生的网络或解析错误的错误对象。
 @return The image download receipt for the data task if available. `nil` if the image is stored in the cache.
 cache and the URL request cache policy allows the cache to be used.
 // 数据任务的图像下载收据(如果存在)。 如果图像存储在缓存中,则为nil。 缓存和URL请求缓存策略允许使用缓存。
 */
- (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;

/**
 Creates a data task using the `sessionManager` instance for the specified URL request.

 If the same data task is already in the queue or currently being downloaded, the success and failure blocks are
 appended to the already existing task. Once the task completes, all success or failure blocks attached to the
 task are executed in the order they were added.
// 使用sessionManager实例为指定的URL请求创建一个数据任务。

  如果相同的数据任务已经在队列中或当前正在下载,则成功和失败block
  附加到已经存在的任务。 一旦任务完成,所有成功或失败block附加到
  任务并按照它们添加的顺序执行。

 @param request The URL request.
 @param receiptID The identifier to use for the download receipt that will be created for this request. This must be a unique identifier that does not represent any other request.
// 用于为此请求创建的下载收据的标识符。 这必须是不代表任何其他请求的唯一标识符。

 @param success A block to be executed when the image data task finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the response parameter will be `nil`.
 @param failure A block object to be executed when the image data task finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred.

 @return The image download receipt for the data task if available. `nil` if the image is stored in the cache.
 cache and the URL request cache policy allows the cache to be used.
 */
- (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;

/**
 Cancels the data task in the receipt by removing the corresponding success and failure blocks and cancelling the data task if necessary.

 If the data task is pending in the queue, it will be cancelled if no other success and failure blocks are registered with the data task. If the data task is currently executing or is already completed, the success and failure blocks are removed and will not be called when the task finishes.
// 通过删除相应的成功和失败块并在必要时取消数据任务来取消收据中的数据任务。

 如果数据任务在队列中待处理,如果没有其他成功和失败块向数据任务注册,
 则它将被取消。 如果数据任务当前正在执行或已经完成,则成功和失败块将被删除,
 并且在任务完成时不会被调用。

 @param imageDownloadReceipt The image download receipt to cancel.
 */
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt;

@end

AFImageDownloader类负责在优先队列中并行下载图像。 根据下载优先级,传入的下载将被添加到队列的前面或后面。 每个下载的图像都缓存在底层的NSURLCache以及内存中的图像缓存中。 默认情况下,任何具有图像缓存中等效缓存图像的下载请求都将自动提供缓存图像表示。

2. AFImageDownloadReceipt

先看一个AFImageDownloadReceipt.h中的接口。

typedef NS_ENUM(NSInteger, AFImageDownloadPrioritization) {
    AFImageDownloadPrioritizationFIFO,
    AFImageDownloadPrioritizationLIFO
};

/**
 The `AFImageDownloadReceipt` is an object vended by the `AFImageDownloader` when starting a data task. It can be used to cancel active tasks running on the `AFImageDownloader` session. As a general rule, image data tasks should be cancelled using the `AFImageDownloadReceipt` instead of calling `cancel` directly on the `task` itself. The `AFImageDownloader` is optimized to handle duplicate task scenarios as well as pending versus active downloads.
 */
@interface AFImageDownloadReceipt : NSObject

/**
 The data task created by the `AFImageDownloader`.
*/
@property (nonatomic, strong) NSURLSessionDataTask *task;

/**
 The unique identifier for the success and failure blocks when duplicate requests are made.
 */
@property (nonatomic, strong) NSUUID *receiptID;

@end

AFImageDownloadReceipt是启动数据任务时由AFImageDownloader提供的对象。 它可以用来取消在AFImageDownloader会话中运行的活动任务。 作为一般规则,应该使用AFImageDownloadReceipt来取消图像数据任务,而不是直接在task本身上调用cancelAFImageDownloader经过优化,可以处理重复的任务场景以及待处理和活动下载。


AFImageDownloader初始化

下面我们先看一下AFImageDownloader的初始化。

它的初始化就是一个单例

+ (instancetype)defaultInstance {
    static AFImageDownloader *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

然后重写了下init方法。

- (instancetype)init {
    // 调用defaultURLSessionConfiguration类方法进行配置
    NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration];
    return [self initWithSessionConfiguration:defaultConfiguration];
}
+ (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;
}

大家看一下这里都是常规的配置。

然后就是直接调用下面的方法进行初始化。

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
    sessionManager.responseSerializer = [AFImageResponseSerializer serializer];

    return [self initWithSessionManager:sessionManager
                 downloadPrioritization:AFImageDownloadPrioritizationFIFO
                 maximumActiveDownloads:4
                             imageCache:[[AFAutoPurgingImageCache alloc] init]];
}

- (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;

        // 串行队列,[[NSUUID UUID] UUIDString]保证唯一性
        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;
}

这里实例化了两个队列,分别是串行队列,另外一个是并行队列。队列名字包含了字符串[[NSUUID UUID] UUIDString],保证了唯一性。


图像的下载

下面我们就看一下图像的下载过程。

- (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;

- (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;

一共就上面两个下载方法。

- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
                                                        success:(void (^)(NSURLRequest * _Nonnull, NSHTTPURLResponse * _Nullable, UIImage * _Nonnull))success
                                                        failure:(void (^)(NSURLRequest * _Nonnull, NSHTTPURLResponse * _Nullable, NSError * _Nonnull))failure {
    return [self downloadImageForURLRequest:request withReceiptID:[NSUUID UUID] success:success failure:failure];
}

这个方法实现上直接调用第二个方法,默认的ReceiptID参数传递[NSUUID UUID]

- (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];
              // 如果request.URL = nil,就在主线程回调 failure      
             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;

        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;
                               AFImageDownloaderMergedTask *mergedTask = strongSelf.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 {
                                       if ([strongSelf.imageCache shouldCacheImage:responseObject forRequest:request withAdditionalIdentifier:nil]) {
                                           [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;
    }
}

主要做了下面几个工作:

1. 向预先存在请求中添加成功失败回调块

如果成功和失败块已经存在,则将其添加到预先存在的请求中,主要对应下面这段代码。

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

这里AFImageDownloaderMergedTask是一个新的类,先看一下API文档。

@interface AFImageDownloaderMergedTask : NSObject

@property (nonatomic, strong) NSString *URLIdentifier;
@property (nonatomic, strong) NSUUID *identifier;
@property (nonatomic, strong) NSURLSessionDataTask *task;
@property (nonatomic, strong) NSMutableArray  *responseHandlers;

@end

@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

这里有一个数组responseHandlers里面存放的队形类型就是AFImageDownloaderResponseHandler

@property (nonatomic, strong) NSMutableArray  *responseHandlers;
@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]];
}

@end

这个AFImageDownloaderResponseHandler对象将UUID、成功回调和失败回调作为属性包了进去,方便使用。

下面我们还是回来到下载图像的源代码中。

这里有一个字典,URLIdentifier作为key,取出来的就是AFImageDownloaderMergedTask对象。

@property (nonatomic, strong) NSMutableDictionary *mergedTasks;

如果请求任务已经存在,那么就实例化AFImageDownloaderResponseHandler对象并添加到数组中。并且进行赋值task = existingMergedTask.task,取出来存在的task传给自定义的task对象。最后return返回。

2. 从缓存中添加图像

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

这段代码还是好理解吧,利用下面方法取出缓存图像,并且在NSURLRequestUseProtocolCachePolicyNSURLRequestReturnCacheDataElseLoadNSURLRequestReturnCacheDataDontLoad情况下,取出来图像,如果不为空就调用success的回调。

- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;

3. 创建请求,设置权限验证和响应序列化

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;
                       AFImageDownloaderMergedTask *mergedTask = strongSelf.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 {
                               if ([strongSelf.imageCache shouldCacheImage:responseObject forRequest:request withAdditionalIdentifier:nil]) {
                                   [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];
                   });
               }];

调用AFHTTPSessionManager类中的方法返回NSURLSessionDataTask *createdTask对象。

这里uploadProgressdownloadProgress都传递为nil,并且在回调完成的方法中,生成异步并行队列进行处理。

这里首先取出对象AFImageDownloaderMergedTask *mergedTask,然后根据[mergedTask.identifier isEqual:mergedTaskIdentifier]进行比较,满足了以后调用下面方法返回mergedTask对象,并将其对应的key从字典中移除。

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

接着就是根据回调error参数,进行判断

如果error不为空,也就是存在错误

if (error) {
   for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
       if (handler.failureBlock) {
           dispatch_async(dispatch_get_main_queue(), ^{
               handler.failureBlock(request, (NSHTTPURLResponse*)response, error);
           });
       }
   }
}

那么就遍历responseHandlers数组,找到其对应的failureBlock属性,并在主线程回调block。

如果error为nil

else {
   if ([strongSelf.imageCache shouldCacheImage:responseObject forRequest:request withAdditionalIdentifier:nil]) {
       [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);
           });
       }
   }
   
}

在这里接着进行了判断,如果需要缓存图像,那么就调用方法进行缓存;遍历responseHandlers数组,找到其对应的successBlock属性,并在主线程回调block。

接着就是

// 减小任务计数器的计数值
[strongSelf safelyDecrementActiveTaskCount];

// 如果需要的话开启下一个任务
[strongSelf safelyStartNextTaskIfNecessary];
// 减小任务计数
- (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;
}

4. 请求完成时存储响应处理程序以备使用

主要对应下面这段代码

// AFImageDownloaderResponseHandler实例化
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
                                                                                           success:success
                                                                                           failure:failure];

// AFImageDownloaderMergedTask实例化
AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
                                           initWithURLIdentifier:URLIdentifier
                                           identifier:mergedTaskIdentifier
                                           task:createdTask];

// 向数组中添加响应
[mergedTask addResponseHandler:handler];
self.mergedTasks[URLIdentifier] = mergedTask;

5. 根据当前的活动请求计数启动请求或将其排入队列

// 启动请求
if ([self isActiveRequestCountBelowMaximumLimit]) {
    [self startMergedTask:mergedTask];
} 
// 排入队列
else {
    [self enqueueMergedTask:mergedTask];
}

task = mergedTask.task;

这里要首先进行判断

- (BOOL)isActiveRequestCountBelowMaximumLimit {
    return self.activeRequestCount < self.maximumActiveDownloads;
}

self.activeRequestCount这个就是活动的计数器,这个self.maximumActiveDownloads值为4,代表最大的下载数。超过这个值就排入队列,小于这个限制就开始任务。

这个思想很值得我们去学习啊~~~

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
    sessionManager.responseSerializer = [AFImageResponseSerializer serializer];

    return [self initWithSessionManager:sessionManager
                 downloadPrioritization:AFImageDownloadPrioritizationFIFO
                 maximumActiveDownloads:4
                             imageCache:[[AFAutoPurgingImageCache alloc] init]];
}

后记

本篇主要讲述了有关图像下载AFImageDownloader

AFNetworking源码探究(二十) —— UIKit相关之AFImageDownloader图像下载(三)_第1张图片

你可能感兴趣的:(AFNetworking源码探究(二十) —— UIKit相关之AFImageDownloader图像下载(三))