AFNetworking源碼探究(二十) —— UIKit相關(guān)之AFImageDownloader圖像下載(三)

版本記錄

版本號(hào) 時(shí)間
V1.0 2018.03.04

前言

我們做APP發(fā)起網(wǎng)絡(luò)請(qǐng)求,都離不開(kāi)一個(gè)非常有用的框架AFNetworking袜硫,可以說(shuō)這個(gè)框架的知名度已經(jīng)超過(guò)了蘋(píng)果的底層網(wǎng)絡(luò)請(qǐng)求部分留荔,很多人可能不知道蘋(píng)果底層是如何發(fā)起網(wǎng)絡(luò)請(qǐng)求的府树,但是一定知道AFNetworking,接下來(lái)幾篇我們就一起詳細(xì)的解析一下這個(gè)框架偎蘸。感興趣的可以看上面寫(xiě)的幾篇庄蹋。
1. AFNetworking源碼探究(一) —— 基本介紹
2. AFNetworking源碼探究(二) —— GET請(qǐng)求實(shí)現(xiàn)之NSURLSessionDataTask實(shí)例化(一)
3. AFNetworking源碼探究(三) —— GET請(qǐng)求實(shí)現(xiàn)之任務(wù)進(jìn)度設(shè)置和通知監(jiān)聽(tīng)(一)
4. AFNetworking源碼探究(四) —— GET請(qǐng)求實(shí)現(xiàn)之代理轉(zhuǎn)發(fā)思想(一)
5. AFNetworking源碼探究(五) —— AFURLSessionManager中NSURLSessionDelegate詳細(xì)解析(一)
6. AFNetworking源碼探究(六) —— AFURLSessionManager中NSURLSessionTaskDelegate詳細(xì)解析(一)
7. AFNetworking源碼探究(七) —— AFURLSessionManager中NSURLSessionDataDelegate詳細(xì)解析(一)
8. AFNetworking源碼探究(八) —— AFURLSessionManager中NSURLSessionDownloadDelegate詳細(xì)解析(一)
9. AFNetworking源碼探究(九) —— AFURLSessionManagerTaskDelegate中三個(gè)轉(zhuǎn)發(fā)代理方法詳細(xì)解析(一)
10. AFNetworking源碼探究(十) —— 數(shù)據(jù)解析之?dāng)?shù)據(jù)解析架構(gòu)的分析(一)
11. AFNetworking源碼探究(十一) —— 數(shù)據(jù)解析之子類(lèi)中協(xié)議方法的實(shí)現(xiàn)(二)
12. AFNetworking源碼探究(十二) —— 數(shù)據(jù)解析之子類(lèi)中協(xié)議方法的實(shí)現(xiàn)(三)
13. AFNetworking源碼探究(十三) —— AFSecurityPolicy與安全認(rèn)證 (一)
14. AFNetworking源碼探究(十四) —— AFSecurityPolicy與安全認(rèn)證 (二)
15. AFNetworking源碼探究(十五) —— 請(qǐng)求序列化之架構(gòu)分析(一)
16. AFNetworking源碼探究(十六) —— 請(qǐng)求序列化之協(xié)議方法的實(shí)現(xiàn)(二)
17. AFNetworking源碼探究(十七) —— _AFURLSessionTaskSwizzling實(shí)現(xiàn)方法交換(轉(zhuǎn)載)(一)
18. AFNetworking源碼探究(十八) —— UIKit相關(guān)之AFNetworkActivityIndicatorManager(一)
19. AFNetworking源碼探究(十九) —— UIKit相關(guān)之幾個(gè)分類(lèi)(二)

回顧

上一篇主要介紹了AFNetworkActivityIndicatorManager這個(gè)與UIKit相關(guān)的類(lèi),這一篇主要介紹AFImageDownloader有關(guān)圖像的下載迷雪。


AFImageDownloader文件

我們先看一下這個(gè)文件限书,這個(gè)文件包含兩個(gè)類(lèi)。分別為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.
 */
// 圖像緩存默認(rèn)情況下用于將所有下載的圖像存儲(chǔ)在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。 默認(rèn)情況下赁严,
// 這是通過(guò)一個(gè)AFImageResponseSerializer和一個(gè)共享的NSURLCache來(lái)配置所有的圖片下載扰柠。
@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;

/**
 Defines the order prioritization of incoming download requests being inserted into the queue. `AFImageDownloadPrioritizationFIFO` by default.
 */
// 定義插入隊(duì)列的傳入下載請(qǐng)求的順序優(yōu)先級(jí)。 默認(rèn)情況下為AFImageDownloadPrioritizationFIFO疼约。
@property (nonatomic, assign) AFImageDownloadPrioritization downloadPrioritizaton;

/**
 The shared default instance of `AFImageDownloader` initialized with default values.
 */
// AFImageDownloader的共享默認(rèn)實(shí)例使用默認(rèn)值初始化卤档。
+ (instancetype)defaultInstance;

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

 @returns The default `NSURLCache` instance.
 */
// 使用常用的使用參數(shù)值創(chuàng)建一個(gè)默認(rèn)的NSURLCache
+ (NSURLCache *)defaultURLCache;

/**
 The default `NSURLSessionConfiguration` with common usage parameter values.
 */
// 默認(rèn)的NSURLSessionConfiguration具有常用的使用參數(shù)值。
+ (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.
// 使用給定的會(huì)話(huà)管理器程剥、下載優(yōu)先級(jí)劝枣,最大活動(dòng)下載計(jì)數(shù)和圖像緩存
// 來(lái)初始化AFImageDownloader實(shí)例。

 @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實(shí)例為指定的URL請(qǐng)求創(chuàng)建一個(gè)數(shù)據(jù)任務(wù)。

  如果相同的數(shù)據(jù)任務(wù)已經(jīng)在隊(duì)列中或當(dāng)前正在下載舔腾,則成功和失敗模塊是
  附加到已經(jīng)存在的任務(wù)溪胶。 一旦任務(wù)完成,所有成功或失敗塊附加到
  任務(wù)并按照它們添加的順序執(zhí)行琢唾。

 @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`.
 // 當(dāng)圖像數(shù)據(jù)任務(wù)成功完成時(shí)要執(zhí)行的塊。 該塊沒(méi)有返回值盾饮,并且有三個(gè)參數(shù):
 // 客戶(hù)端發(fā)送的請(qǐng)求采桃,從服務(wù)器收到的響應(yīng)以及從請(qǐng)求響應(yīng)數(shù)據(jù)創(chuàng)建的圖像。 
 // 如果圖像是從緩存中返回的丘损,則響應(yīng)參數(shù)將為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.
 // 當(dāng)圖像數(shù)據(jù)任務(wù)完成失敗或成功完成時(shí)要執(zhí)行的塊對(duì)象。 
 // 該塊沒(méi)有返回值徘钥,并且有三個(gè)參數(shù):客戶(hù)端發(fā)送的請(qǐng)求衔蹲,
 // 從服務(wù)器接收到的響應(yīng)以及描述發(fā)生的網(wǎng)絡(luò)或解析錯(cuò)誤的錯(cuò)誤對(duì)象。
 @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.
 // 數(shù)據(jù)任務(wù)的圖像下載收據(jù)(如果存在)呈础。 如果圖像存儲(chǔ)在緩存中舆驶,則為nil。 緩存和URL請(qǐng)求緩存策略允許使用緩存而钞。
 */
- (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實(shí)例為指定的URL請(qǐng)求創(chuàng)建一個(gè)數(shù)據(jù)任務(wù)沙廉。

  如果相同的數(shù)據(jù)任務(wù)已經(jīng)在隊(duì)列中或當(dāng)前正在下載,則成功和失敗block
  附加到已經(jīng)存在的任務(wù)臼节。 一旦任務(wù)完成撬陵,所有成功或失敗block附加到
  任務(wù)并按照它們添加的順序執(zhí)行。

 @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.
// 用于為此請(qǐng)求創(chuàng)建的下載收據(jù)的標(biāo)識(shí)符网缝。 這必須是不代表任何其他請(qǐng)求的唯一標(biāo)識(shí)符巨税。

 @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.
// 通過(guò)刪除相應(yīng)的成功和失敗塊并在必要時(shí)取消數(shù)據(jù)任務(wù)來(lái)取消收據(jù)中的數(shù)據(jù)任務(wù)。

 如果數(shù)據(jù)任務(wù)在隊(duì)列中待處理粉臊,如果沒(méi)有其他成功和失敗塊向數(shù)據(jù)任務(wù)注冊(cè)草添,
 則它將被取消。 如果數(shù)據(jù)任務(wù)當(dāng)前正在執(zhí)行或已經(jīng)完成扼仲,則成功和失敗塊將被刪除果元,
 并且在任務(wù)完成時(shí)不會(huì)被調(diào)用。

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

@end

AFImageDownloader類(lèi)負(fù)責(zé)在優(yōu)先隊(duì)列中并行下載圖像犀盟。 根據(jù)下載優(yōu)先級(jí)而晒,傳入的下載將被添加到隊(duì)列的前面或后面。 每個(gè)下載的圖像都緩存在底層的NSURLCache以及內(nèi)存中的圖像緩存中阅畴。 默認(rèn)情況下倡怎,任何具有圖像緩存中等效緩存圖像的下載請(qǐng)求都將自動(dòng)提供緩存圖像表示。

2. AFImageDownloadReceipt

先看一個(gè)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是啟動(dòng)數(shù)據(jù)任務(wù)時(shí)由AFImageDownloader提供的對(duì)象监署。 它可以用來(lái)取消在AFImageDownloader會(huì)話(huà)中運(yùn)行的活動(dòng)任務(wù)颤专。 作為一般規(guī)則栖榨,應(yīng)該使用AFImageDownloadReceipt來(lái)取消圖像數(shù)據(jù)任務(wù)炬转,而不是直接在task本身上調(diào)用cancel矛市。 AFImageDownloader經(jīng)過(guò)優(yōu)化啃憎,可以處理重復(fù)的任務(wù)場(chǎng)景以及待處理和活動(dòng)下載捅彻。


AFImageDownloader初始化

下面我們先看一下AFImageDownloader的初始化业踏。

它的初始化就是一個(gè)單例

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

然后重寫(xiě)了下init方法划滋。

- (instancetype)init {
    // 調(diào)用defaultURLSessionConfiguration類(lèi)方法進(jìn)行配置
    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;
}

大家看一下這里都是常規(guī)的配置杜漠。

然后就是直接調(diào)用下面的方法進(jìn)行初始化俏拱。

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

        // 串行隊(duì)列暑塑,[[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);

        // 并行隊(duì)列
        name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]];
        self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
    }

    return self;
}

這里實(shí)例化了兩個(gè)隊(duì)列,分別是串行隊(duì)列锅必,另外一個(gè)是并行隊(duì)列事格。隊(duì)列名字包含了字符串[[NSUUID UUID] UUIDString],保證了唯一性搞隐。


圖像的下載

下面我們就看一下圖像的下載過(guò)程驹愚。

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

一共就上面兩個(gè)下載方法。

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

這個(gè)方法實(shí)現(xiàn)上直接調(diào)用第二個(gè)方法劣纲,默認(rèn)的ReceiptID參數(shù)傳遞[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 {
    // 這里是在串行隊(duì)列上進(jìn)行處理,隊(duì)列已經(jīng)進(jìn)行了初始化
    __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味廊,就在主線(xiàn)程回調(diào) 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;
    }
}

主要做了下面幾個(gè)工作:

1. 向預(yù)先存在請(qǐng)求中添加成功失敗回調(diào)塊

如果成功和失敗塊已經(jīng)存在蒸甜,則將其添加到預(yù)先存在的請(qǐng)求中,主要對(duì)應(yīng)下面這段代碼余佛。

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是一個(gè)新的類(lèi)柠新,先看一下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

// 初始化對(duì)象
- (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;
}

// 數(shù)組添加對(duì)象
- (void)addResponseHandler:(AFImageDownloaderResponseHandler*)handler {
    [self.responseHandlers addObject:handler];
}

// 數(shù)組移除對(duì)象
- (void)removeResponseHandler:(AFImageDownloaderResponseHandler*)handler {
    [self.responseHandlers removeObject:handler];
}

@end

這里有一個(gè)數(shù)組responseHandlers里面存放的隊(duì)形類(lèi)型就是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

這個(gè)AFImageDownloaderResponseHandler對(duì)象將UUID恨憎、成功回調(diào)和失敗回調(diào)作為屬性包了進(jìn)去,方便使用郊楣。

下面我們還是回來(lái)到下載圖像的源代碼中憔恳。

這里有一個(gè)字典,URLIdentifier作為key净蚤,取出來(lái)的就是AFImageDownloaderMergedTask對(duì)象钥组。

@property (nonatomic, strong) NSMutableDictionary *mergedTasks;

如果請(qǐng)求任務(wù)已經(jīng)存在,那么就實(shí)例化AFImageDownloaderResponseHandler對(duì)象并添加到數(shù)組中今瀑。并且進(jìn)行賦值task = existingMergedTask.task程梦,取出來(lái)存在的task傳給自定義的task對(duì)象点把。最后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情況下挺份,取出來(lái)圖像褒翰,如果不為空就調(diào)用success的回調(diào)。

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

3. 創(chuàng)建請(qǐng)求匀泊,設(shè)置權(quán)限驗(yàn)證和響應(yīng)序列化

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

調(diào)用AFHTTPSessionManager類(lèi)中的方法返回NSURLSessionDataTask *createdTask對(duì)象优训。

這里uploadProgressdownloadProgress都傳遞為nil,并且在回調(diào)完成的方法中探赫,生成異步并行隊(duì)列進(jìn)行處理型宙。

這里首先取出對(duì)象AFImageDownloaderMergedTask *mergedTask撬呢,然后根據(jù)[mergedTask.identifier isEqual:mergedTaskIdentifier]進(jìn)行比較伦吠,滿(mǎn)足了以后調(diào)用下面方法返回mergedTask對(duì)象,并將其對(duì)應(yīng)的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;
}

接著就是根據(jù)回調(diào)error參數(shù)毛仪,進(jìn)行判斷

如果error不為空,也就是存在錯(cuò)誤

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

那么就遍歷responseHandlers數(shù)組芯勘,找到其對(duì)應(yīng)的failureBlock屬性箱靴,并在主線(xiàn)程回調(diào)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);
           });
       }
   }
   
}

在這里接著進(jìn)行了判斷荷愕,如果需要緩存圖像衡怀,那么就調(diào)用方法進(jìn)行緩存;遍歷responseHandlers數(shù)組安疗,找到其對(duì)應(yīng)的successBlock屬性抛杨,并在主線(xiàn)程回調(diào)block。

接著就是

// 減小任務(wù)計(jì)數(shù)器的計(jì)數(shù)值
[strongSelf safelyDecrementActiveTaskCount];

// 如果需要的話(huà)開(kāi)啟下一個(gè)任務(wù)
[strongSelf safelyStartNextTaskIfNecessary];
// 減小任務(wù)計(jì)數(shù)
- (void)safelyDecrementActiveTaskCount {
    dispatch_sync(self.synchronizationQueue, ^{
        if (self.activeRequestCount > 0) {
            self.activeRequestCount -= 1;
        }
    });
}

// 根據(jù)需要增加任務(wù)計(jì)數(shù)
- (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. 請(qǐng)求完成時(shí)存儲(chǔ)響應(yīng)處理程序以備使用

主要對(duì)應(yīng)下面這段代碼

// AFImageDownloaderResponseHandler實(shí)例化
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
                                                                                           success:success
                                                                                           failure:failure];

// AFImageDownloaderMergedTask實(shí)例化
AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
                                           initWithURLIdentifier:URLIdentifier
                                           identifier:mergedTaskIdentifier
                                           task:createdTask];

// 向數(shù)組中添加響應(yīng)
[mergedTask addResponseHandler:handler];
self.mergedTasks[URLIdentifier] = mergedTask;

5. 根據(jù)當(dāng)前的活動(dòng)請(qǐng)求計(jì)數(shù)啟動(dòng)請(qǐng)求或?qū)⑵渑湃腙?duì)列

// 啟動(dòng)請(qǐng)求
if ([self isActiveRequestCountBelowMaximumLimit]) {
    [self startMergedTask:mergedTask];
} 
// 排入隊(duì)列
else {
    [self enqueueMergedTask:mergedTask];
}

task = mergedTask.task;

這里要首先進(jìn)行判斷

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

self.activeRequestCount這個(gè)就是活動(dòng)的計(jì)數(shù)器荐类,這個(gè)self.maximumActiveDownloads值為4怖现,代表最大的下載數(shù)。超過(guò)這個(gè)值就排入隊(duì)列玉罐,小于這個(gè)限制就開(kāi)始任務(wù)屈嗤。

這個(gè)思想很值得我們?nèi)W(xué)習(xí)啊~~~

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

后記

本篇主要講述了有關(guān)圖像下載AFImageDownloader

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吊输,一起剝皮案震驚了整個(gè)濱河市饶号,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌季蚂,老刑警劉巖讨韭,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脂信,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡透硝,警方通過(guò)查閱死者的電腦和手機(jī)狰闪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)濒生,“玉大人埋泵,你說(shuō)我怎么就攤上這事∽镏危” “怎么了丽声?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)觉义。 經(jīng)常有香客問(wèn)我雁社,道長(zhǎng),這世上最難降的妖魔是什么晒骇? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任霉撵,我火速辦了婚禮,結(jié)果婚禮上洪囤,老公的妹妹穿的比我還像新娘徒坡。我一直安慰自己,他們只是感情好瘤缩,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布喇完。 她就那樣靜靜地躺著,像睡著了一般剥啤。 火紅的嫁衣襯著肌膚如雪锦溪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,842評(píng)論 1 290
  • 那天府怯,我揣著相機(jī)與錄音刻诊,去河邊找鬼。 笑死富腊,一個(gè)胖子當(dāng)著我的面吹牛坏逢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赘被,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼是整,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了民假?” 一聲冷哼從身側(cè)響起浮入,我...
    開(kāi)封第一講書(shū)人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎羊异,沒(méi)想到半個(gè)月后事秀,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體彤断,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年易迹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宰衙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡睹欲,死狀恐怖供炼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情窘疮,我是刑警寧澤袋哼,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站闸衫,受9級(jí)特大地震影響涛贯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蔚出,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一弟翘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧身冬,春花似錦衅胀、人聲如沸岔乔。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)雏门。三九已至嘿歌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間茁影,已是汗流浹背宙帝。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留募闲,地道東北人步脓。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像浩螺,于是被迫代替她去往敵國(guó)和親靴患。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容