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 <AFImageRequestCache> 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 <AFImageRequestCache>)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 <AFImageRequestCache>)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 <AFImageDownloaderResponseHandler*> *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 <AFImageDownloaderResponseHandler*> *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: @"<AFImageDownloaderResponseHandler>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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容