AFNetworking 3.0 源码解读(八)之 AFImageDownloader

AFImageDownloader 这个类对写DownloadManager有很大的借鉴意义。在平时的开发中,当我们使用UIImageView加载一个网络上的图片时,其原理就是把图片下载下来,然后再赋值。这也是AFImageDownloader这个类的核心功能。

前言

AFImageDownloader 专门管理一组图片的下载任务。试想,如果有一个界面的功能是对图片进行处理,要求下载高清图片,那么这个AFImageDownloader就用上了。

1.AFImageDownloadPrioritization

AFImageDownloadPrioritization 表示图片下载的优先级:

  • AFImageDownloadPrioritizationFIFO 表示先进先出
  • AFImageDownloadPrioritizationLIFO 表示后进先出

2.AFImageDownloadReceipt

AFImageDownloadReceipt 表示一个下载依据。也就是对一个下载任务的封装。它有2个属性:

  • NSURLSessionDataTask *task;
  • NSUUID *receiptID;

其中 task 表示一个下载任务。一般来说,如果我们要对下载的任务进行操作,就是用这个 task 来完成,并不使用AFImageDownloader来操作。通过[NSUUID UUID]来生成一个唯一的标识来证明AFImageDownloadReceipt的身份。

3.AFImageDownloaderResponseHandler

AFImageDownloaderResponseHandler 用来处理图片下载完成后的事件。他有三个属性:

  • @property (nonatomic, strong) NSUUID *uuid; 和AFImageDownloadReceip的receiptID一致
  • void (^successBlock)(NSURLRequest*, NSHTTPURLResponse*, UIImage*); 下载成功后的回调
  • void (^failureBlock)(NSURLRequest*, NSHTTPURLResponse*, NSError*); 下载失败后的回调

这个AFImageDownloaderResponseHandler在下边的使用中会被加到一个数组中。如果同一个下载请求写了两个Handler,那么这两个Handler会按顺序依次调用。这是后话,下边也会说到。值得注意的是:不论请求成功还是失败,都是在主线程调用的

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

4.AFImageDownloaderMergedTask

AFImageDownloaderMergedTask 封装了对同一个请求的处理。同下载任务一样,也存在下载的URL重复的情况,这种情况需要做特殊处理。这里是一个值得借鉴的地方。那么如何判定是一个请求呢?AFImageDownloader是通过request.URL.absoluteString来进行判断的。

我们看看它的属性有哪些?

  • NSString *URLIdentifier URL标识
  • NSUUID *identifier 唯一标识
  • NSURLSessionDataTask *task 任务task
  • NSMutableArray <AFImageDownloaderResponseHandler*> *responseHandlers 回调的数组,上边已经解释过了。如果两个请求是相同的。会请求同一份数据,但是两个请求的回调都会调用。调用的顺序就是按照这个数组的排列顺序。

--

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

5.AFImageDownloader

AFImageDownloader是构成获取图片数据的核心方法。我们可以设置图片的最大并发数。其中最需要注意的就是如何处理多次重复的请求和请求后数据的去处问题。我们先了解下AFImageDownloader暴露出来的属性:

  • id <AFImageRequestCache> imageCache 图片缓存的地方
  • AFHTTPSessionManager *sessionManager 通过这个属性来获取图片数据
  • AFImageDownloadPrioritization downloadPrioritizaton 下载的优先级。
  • initWithSessionManager:downloadPrioritization:maximumActiveDownloads:imageCache: 初始化的时候配置
  • downloadImageForURLRequest:success:failure:
  • downloadImageForURLRequest:withReceiptID:success:failure: 指定receiptID
  • cancelTaskForImageDownloadReceipt: 取消一个请求

AFImageDownloader.m中新增了下边这几个属性:

  • dispatch_queue_t synchronizationQueue
  • dispatch_queue_t responseQueue
  • NSInteger maximumActiveDownloads 支持的最大的同时下载数
  • NSInteger activeRequestCount 激活的请求数
  • NSMutableArray *queuedMergedTasks 队列中的task
  • NSMutableDictionary *mergedTasks 合并的队列

<font color=orange>补充1:</font>

大概说下 dispatch_sync()dispatch_async() 这两个方法。

dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_sync(concurrentQueue, ^(){
        NSLog(@"2");
        [NSThread sleepForTimeInterval:5];
        NSLog(@"3");
    });
    NSLog(@"4");

输出为:

2016-08-25 11:50:51.601 xxxx[1353:102804] 1
2016-08-25 11:50:51.601 xxxx[1353:102804] 2
2016-08-25 11:50:56.603 xxxx[1353:102804] 3
2016-08-25 11:50:56.603 xxxx[1353:102804] 4

再看:

dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"2");
        [NSThread sleepForTimeInterval:5];
        NSLog(@"3");
    });
    NSLog(@"4");

输出为:

2016-08-25 11:52:29.022 xxxx[1392:104246] 1
2016-08-25 11:52:29.023 xxxx[1392:104246] 4
2016-08-25 11:52:29.023 xxxx[1392:104284] 2
2016-08-25 11:52:34.029 xxxx[1392:104284] 3

通过上边的两个例子,我们可以总结出:

  • dispatch_sync(),同步添加操作。他是等待添加进队列里面的操作完成之后再继续执行。
  • dispatch_async ,异步添加进任务队列,它不会做任何等待

<font color=orange>补充2:</font>

我们简单介绍下NSURLCache。NSURLCache 为您的应用的 URL 请求提供了内存中以及磁盘上的综合缓存机制。网络缓存减少了需要向服务器发送请求的次数,同时也提升了离线或在低速网络中使用应用的体验。当一个请求完成下载来自服务器的回应,一个缓存的回应将在本地保存。下一次同一个请求再发起时,本地保存的回应就会马上返回,不需要连接服务器。NSURLCache 会 自动 且 透明 地返回回应。

为了好好利用 NSURLCache,你需要初始化并设置一个共享的 URL 缓存。在 iOS 中这项工作需要在 -application:didFinishLaunchingWithOptions: 完成,而 OS X 中是在 –applicationDidFinishLaunching::

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
                                                       diskCapacity:20 * 1024 * 1024
                                                           diskPath:nil];
  [NSURLCache setSharedURLCache:URLCache];
}

NSURLRequest 有个 cachePolicy 属性,我们平时最常用的有四个属性:

  • NSURLRequestUseProtocolCachePolicy: 对特定的 URL 请求使用网络协议中实现的缓存逻辑。这是默认的策略。
  • NSURLRequestReloadIgnoringLocalCacheData:数据需要从原始地址加载。不使用现有缓存。
  • NSURLRequestReturnCacheDataElseLoad:无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么从原始地址加载数据
  • NSURLRequestReturnCacheDataDontLoad:无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么放弃从原始地址加载数据,请求视为失败(即:“离线”模式)。

// 设置默认的URLCache 大小为内存中20M 硬盘150M
+ (NSURLCache *)defaultURLCache {
    return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
                                         diskCapacity:150 * 1024 * 1024
                                             diskPath:@"com.alamofire.imagedownloader"];
}

--

// 返回默认的NSURLSessionConfiguration
+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration {
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];

    //TODO set the default HTTP headers

    // 接受Cookies
    configuration.HTTPShouldSetCookies = YES;
    // 由于并不是所有的服务器都支持管线化,所以默认的这个属性设置为NO
    configuration.HTTPShouldUsePipelining = NO;

    // 设置默认的缓存策略为NSURLRequestUseProtocolCachePolicy
    configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
    // 允许使用蜂窝网络访问
    configuration.allowsCellularAccess = YES;
    
    // 设置超时时间为60妙
    configuration.timeoutIntervalForRequest = 60.0;
    
    // 设置URLCache 策略
    configuration.URLCache = [AFImageDownloader defaultURLCache];

    return configuration;
}

--

// 初始化方法
- (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;

        // 初始化synchronizationQueue 这个队列是串行的
        NSString *name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.synchronizationqueue-%@", [[NSUUID UUID] UUIDString]];
        self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);

        // 初始化responseQueue 这个队列是并行的
        name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]];
        self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
    }

    return self;
}

--

// 重写init方法
- (instancetype)init {
    
    // 采用默认配置
    NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration];
    // 根据defaultConfiguration初始化一个AFHTTPSessionManager对象
    AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:defaultConfiguration];
    
    // 设置sessionManager的响应序列化为AFImageResponseSerializer(Image)
    sessionManager.responseSerializer = [AFImageResponseSerializer serializer];

    // 调用初始化方法
    return [self initWithSessionManager:sessionManager
                 downloadPrioritization:AFImageDownloadPrioritizationFIFO
                 maximumActiveDownloads:4
                             imageCache:[[AFAutoPurgingImageCache alloc] init]];
}

--

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

好了,通过上边3个初始化方法,我们能够简单的创建出AFImageDownloader对象了,接下来我们要做的事就是实现AFImageDownloader中下载图片的方法了。注意:我们所下载的图片对象最终被封装成AFImageDownloadReceipt

由于下边的这个方法是核心方法,所以我采用了橙色字体来进行描述,我们将会把一个方法进行详细的剖析。

要进行剖析的方法名称,为了后边的描述,我们姑且称这个方法为 <font color=black>核心方法</font>

该方法返回的是AFImageDownloadReceipt,我们看AFImageDownloadReceipt的初始化方法中都需要什么:

 - (instancetype)initWithReceiptID:(NSUUID *)receiptID task:(NSURLSessionDataTask *)task

可以看出需要两个参数:1.NSURLSessionDataTask 2.receiptID 通过观察上边 核心方法 的参数,receiptID已经有了,NSURLSessionDataTask肯定是由request来生成的。

因此我们先初步写了下边的代码:

 NSURLSessionDataTask *task = nil;
    // 由于request不一定有效,所以可能返回nil
    if (task) {
        return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
    } else {
        return nil;
    }

简单点说,核心方法 最核心的作用就是创建NSURLSessionDataTask,而我们规定,创建是在dispatch_sync中使用self.synchronizationQueue队列创建。我们又添加了如下代码:

NSURLSessionDataTask *task = nil;

/* 新增 */
dispatch_sync(self.synchronizationQueue, ^{


});

// 由于request不一定有效,所以可能返回nil
if (task) {
    return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
    return nil;
}

接下来,我们的代码基本就在这个block中了。我们知道我们使用AFImageDownloadReceipt中的receiptID来证明它的身份,那么同样,我们用request.URL.absoluteString来证明一个task的身份。所以我们就要拿到这个身份,并且验证它是否有效

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];
            dispatch_async(dispatch_get_main_queue(), ^{
                failure(request, nil, error);
            });
        }
        return;
    }

});

// 由于request不一定有效,所以可能返回nil
if (task) {
    return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
    return nil;
}

好了,到这里我们已经有了URLIdentifier。接下来就要处理这个问题。如果我调用了多次 核心方法 ,但是URLIdentifier都一样,也就是说我请求了同一个图片,为了性能,我们的处理思想应该是,共用同一资源但处理各自的响应结果事件。

举个例子,比如说我有 图片1 , 用它我来做3件事,(doSomething1, doSomething2, doSomething3),我发了3个请求,但是我要的结果应该拿到 图片1 ,同时能够单独调用doSomething1, doSomething2, doSomething3。

这就用带了我们上边介绍的 AFImageDownloaderMergedTask 任务合并对象了。我们用它来处理上边说的问题。在 AFImageDownloader 中的self.mergedTasks字典中放着所有的合并的任务。key就是URLIdentifier。

我们需要一个 AFImageDownloaderMergedTask ,按照正常的思路是我们先在self.mergedTasks中取,取出后把事件绑定到AFImageDownloaderMergedTask中,然后在取出它的task。别忘了我们只要得到task就行了。

__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];
            dispatch_async(dispatch_get_main_queue(), ^{
                failure(request, nil, error);
            });
        }
        return;
    }
    
     /* 新增 */
    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;
    }

});

// 由于request不一定有效,所以可能返回nil
if (task) {
    return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
    return nil;
}

在这里说一下return,在这个方法中的return不是结束 核心方法 ,而是跳出dispatch_sync这个线程。那么如果在self.mergedTasks中没有这个task,我们首先应该想到的是在内存中去看看image是否存在,如果存在,我们只需要调用success就行了。

__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];
            dispatch_async(dispatch_get_main_queue(), ^{
                failure(request, nil, error);
            });
        }
        return;
    }
    
    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;
    }
    /* 新增 */
    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;
    }

});

// 由于request不一定有效,所以可能返回nil
if (task) {
    return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
    return nil;
}

接下来相对来说就有点复杂了。我决定先写成伪代码比较好理解。我们必须创建一个task。创建很容易,来看我们创建后的代码。

  __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];
         dispatch_async(dispatch_get_main_queue(), ^{
             failure(request, nil, error);
         });
     }
     return;
 }
 
 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;
 }
 
 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;
 }
 
 /* 新增 */
  NSURLSessionDataTask *createdTask;
 // 创建task
 createdTask = [self.sessionManager
                dataTaskWithRequest:request
                completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                 
                    // 这里先不做处理
                 
                }];

  });

  // 由于request不一定有效,所以可能返回nil
  if (task) {
      return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
  } else {
      return nil;
  }

按理说,到了这里我们已经有了task,但别忘了,我们为了防止重复的请求专门封装了AFImageDownloaderMergedTask这个类。我们还必须用这个task来包装AFImageDownloaderMergedTask,并且添加到 AFImageDownloader 的self.mergedTasks字典中。

 __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];
             dispatch_async(dispatch_get_main_queue(), ^{
                 failure(request, nil, error);
             });
         }
         return;
     }
     
     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;
     }
     
     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;
     }
     
     NSUUID *mergedTaskIdentifier = [NSUUID UUID];
      NSURLSessionDataTask *createdTask;
     // 创建task
     createdTask = [self.sessionManager
                    dataTaskWithRequest:request
                    completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                     
                        // 这里先不做处理
                     
                    }];
     
     /* 新增 */
     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;
 
 });
 
 // 由于request不一定有效,所以可能返回nil
 if (task) {
     return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
 } else {
     return nil;
 }

到现在为止,我们需要的东西基本上都已经齐全了。我们要做的就是开启task了,我们先来看看伪代码:

if (当前请求数小于支持的最大请求数) {
    发起请求
}else {
    按照我们设置的请求优先级对task进行排序
}

我们继续,‘当前请求数小于支持的最大请求数’ 这个伪代码对应下边的代码:

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

'发起请求' 这个伪代码对应下边的代码:

- (void)startMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
    [mergedTask.task resume];
    ++self.activeRequestCount;
}

‘按照我们设置的请求优先级对task进行排序’ 我们规定,当我们在数组中取task时,都是取最前边的值,这么这个伪代码对应下边的代码:

- (void)enqueueMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
    switch (self.downloadPrioritizaton) {
        case AFImageDownloadPrioritizationFIFO:  // 先进先出,添加到数组后边
            [self.queuedMergedTasks addObject:mergedTask];
            break;
        case AFImageDownloadPrioritizationLIFO:  // 后进先出, 添加到数组前边
            [self.queuedMergedTasks insertObject:mergedTask atIndex:0];
            break;
    }
}

替换掉伪代码后我们得到的代码如下:

 __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];
            dispatch_async(dispatch_get_main_queue(), ^{
                failure(request, nil, error);
            });
        }
        return;
    }
    
    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;
    }
    
    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;
    }
    
    NSUUID *mergedTaskIdentifier = [NSUUID UUID];
     NSURLSessionDataTask *createdTask;
    // 创建task
    createdTask = [self.sessionManager
                   dataTaskWithRequest:request
                   completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                    
                       // 这里先不做处理
                    
                   }];
    
    
    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;
    
    /* 新增 */
    if ([self isActiveRequestCountBelowMaximumLimit]) {
        [self startMergedTask:mergedTask];
    } else {
        [self enqueueMergedTask:mergedTask];
    }
    

});

// 由于request不一定有效,所以可能返回nil
if (task) {
    return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
    return nil;
}

ok,大功告成。我们已经把 核心方法 一步一步的实现了。那么最后一点,我们就要专门处理当每个请求完成后的一些事件了。同样我们还是使用伪代码来解释。这些代码放在创建task的那个回调中。

// 创建task
createdTask = [self.sessionManager
           dataTaskWithRequest:request
           completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
            
               // 由于我们把所有的事件处理都绑定在AFImageDownloaderMergedTask中。所以我们第一步要先获取这个对象
               1. 获取AFImageDownloaderMergedTask
               
               // 确保这个mergedTask跟这个createdTask是同一个
               2. 如果mergedTask跟这个createdTask是同一个
               
               // 虽然self.mergedTasks中也能取到mergedTask,但我们最终用到的数据还是来自于数组中取出这个mergedTask,取出后要删除掉
               3.在mergedTask数组中取出这个mergedTask后删除数组中的这个mergedTask
               
               if (错误) {
                  4. 遍历mergedTask中的绑定事件中的错误block
               }else {
                  5. 把图片添加进缓存
                  6. 遍历mergedTask中的绑定事件中的成功block
               }
               
               7.当前的请求数减1
               8.开启下一个请求
               
            
           }];

我们逐一翻译伪代码:

首先 1. 获取AFImageDownloaderMergedTask 对应下边的代码:

AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];

2. 如果mergedTask跟这个createdTask是同一个 对应下边的代码:

if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {}

3.在mergedTask数组中取出这个mergedTask后删除数组中的这个mergedTask 对应下边的代码:

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

4. 遍历mergedTask中的绑定事件中的错误block 对应下边的代码:

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

5. 把图片添加进缓存 对应下边的代码:

// 保存image到缓存中
[strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];

6. 遍历mergedTask中的绑定事件中的成功block 对应下边的代码:

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

7.当前的请求数减1 对应下边的代码:

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

看上去这个 核心方法 占用了这么多的篇幅,其实在实际编码中确实很难考虑的这么详细。我们也是按照目的倒推来进行解读的。那么,最终我们来看看完成的代码:

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

        // 创建task
        createdTask = [self.sessionManager
                       dataTaskWithRequest:request
                       completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                           
                           // 得到响应之后,这里的responseObject为image
                           dispatch_async(self.responseQueue, ^{
                               __strong __typeof__(weakSelf) strongSelf = weakSelf;
                               
                               // 这里为什么又再次验证self.mergedTasks是不是存在这个task呢?这是因为在我们新建了createdTask之后,就把createdTask添加到了self.mergedTasks中,因此,一般情况下调用self.mergedTasks[URLIdentifier]能够得到mergedTask
                               
                               AFImageDownloaderMergedTask *mergedTask = self.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 {
                                       
                                       // 保存image到缓存中
                                       [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;
    }
}

接下来,我们只剩下一个方法了,就是取消一个请求。这个我直接写到代码中了。

- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
    dispatch_sync(self.synchronizationQueue, ^{
        
        // 取出标识
        NSString *URLIdentifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString;
        
        // 取出mergedTask
        AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
        
        // 获取凭证在responseHandlers中的位置
        NSUInteger index = [mergedTask.responseHandlers indexOfObjectPassingTest:^BOOL(AFImageDownloaderResponseHandler * _Nonnull handler, __unused NSUInteger idx, __unused BOOL * _Nonnull stop) {
            return handler.uuid == imageDownloadReceipt.receiptID;
        }];

        if (index != NSNotFound) {
            
            AFImageDownloaderResponseHandler *handler = mergedTask.responseHandlers[index];
            // 移除事件处理
            [mergedTask removeResponseHandler:handler];
            
            // 给出错误
            NSString *failureReason = [NSString stringWithFormat:@"ImageDownloader cancelled URL request: %@",imageDownloadReceipt.task.originalRequest.URL.absoluteString];
            NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey:failureReason};
            NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo];
            if (handler.failureBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    handler.failureBlock(imageDownloadReceipt.task.originalRequest, nil, error);
                });
            }
        }

        // 只有当没有Handler且Suspended,要取消任务然后移除掉mergedTask
        if (mergedTask.responseHandlers.count == 0 && mergedTask.task.state == NSURLSessionTaskStateSuspended) {
            [mergedTask.task cancel];
            [self removeMergedTaskWithURLIdentifier:URLIdentifier];
        }
    });
}

总结

之前一直不太清楚图片缓存的原理是什么?现在大概了解了。通过对本篇中的代码的研究,为我们自己实现一些图片或者其他方面的功能提供了参考。

参考链接

推荐阅读

AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager

AFNetworking 3.0 源码解读(二)之 AFSecurityPolicy

AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization

AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization

AFNetworking 3.0 源码解读(五)之 AFURLSessionManager

AFNetworking 3.0 源码解读(六)之 AFHTTPSessionManager

AFNetworking 3.0 源码解读(七)之 AFAutoPurgingImageCache

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容