AFNetworking的图片缓存(二)

下载实现

上篇文章中,我们对AFN的缓存策略实现做了解析,今天我们来看一下AFN如何实现下载图片.

2.1 AFImageDownloadReceipt

跟其他的图片加载类库相似,AFN对每个请求进行了封装,使用这个类我们进行了对请求进行操作.在AFN中这个类就是AFImageDownloadReceipt,每一个发起的请求都会对应这样的对象.这个对象是有两个属性,

/**
 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;
  • task:由AFImageDownloader创建的数据请求任务,必要时可以对这个任务进行相关处理,比如cancel等;
  • receiptID:主要用于取消请求操作.

2.2 AFImageDownloaderResponseHandler

AFImageDownloaderResponseHandler这个类在AFN中用来封装请求的回调保存.这个类中,有三个属性:

@property (nonatomic, strong) NSUUID *uuid;
@property (nonatomic, copy) void (^successBlock)(NSURLRequest*, NSHTTPURLResponse*, UIImage*);
@property (nonatomic, copy) void (^failureBlock)(NSURLRequest*, NSHTTPURLResponse*, NSError*);
  • uuid:这个作为请求的唯一标记,与AFImageDownloadReceipt中的receiptID保持一致,用来唯一标识特定请求。通过这个标志可以把请求和回调进行一一对应;
  • successBlock:请求的成功回调;
  • failureBlock:请求的失败回调.

2.3 AFImageDownloaderMergedTask

AFImageDownloaderMergedTask该类主要用于合并针对同一资源的发起的重复请求,避免资源浪费.

//资源字符串
@property (nonatomic, strong) NSString *URLIdentifier;
//NSUUID字符串,用于结果返回时匹配对应的请求,方便调用回调结果
@property (nonatomic, strong) NSUUID *identifier;
//资源字符串对应的数据请求任务
@property (nonatomic, strong) NSURLSessionDataTask *task;
//URLIdentifier对应的多个请求
@property (nonatomic, strong) NSMutableArray <AFImageDownloaderResponseHandler*> *responseHandlers;
  • URLIdentifier:每个请求发起时均会产生一个被封装了回调结果mergedTask(AFImageDownloaderMergedTask对象),然后被保存在全局mergedTasks中.当新的请求request准备发起时,会使用URLIdentifier与request.URL.absoluteString做比较:判断准备发起的请求与已经存在的mergedTask是否指向同一个资源,如果是则合并该回调结果对象到已经存在的mergedTask的responseHandlers属性中,不存在则重新创建并保存在全局的mergedTasks;
  • identifier:任务的标志,等待收到结果时匹配具体任务,然后调用保存的回调结果;
  • task:发起该资源请求的任务对象;
  • responseHandlers:URLIdentifier对应资源的回调集合,用于获取到结果时进行遍历回调.

??这里或许有人会有疑问,既然为什么有了URLIdentifier作为标志符号来区分请求,为什么还需要一个identifier??

其实这里作者做了一个更细致化的处理.我们知道其实每个请求resuest都会生成一个handler(AFImageDownloaderResponseHandler对象),同时生成一个receipt(AFImageDownloadReceipt对象),所以他们有一个共同的标志,那就是uuid.而对于同一资源的request来讲,是mergedTask(AFImageDownloaderMergedTask对象)将他们链接起来,并将同一个资源请求下的回调全部保存在mergedTask中.那么问题来了:如果在某种需求下,我对同一链接资源发送了多次请求,但是只想要取消某个请求,另外两次的请求我并不想取消怎么办呢?我们根据URLIdentifier只能获取到mergedTask,但是mergedTask保存了这三个请求的回调,所以只时候就需要区分一下请求的回调,来确定哪个回调需要提前取消掉的,而不用等待请求结果.

 

2.4 AFImageDownloader

AFImageDownloader是AFN可以直接用于加载图片资源的实现类.

//任务串行队类
@property (nonatomic, strong) dispatch_queue_t synchronizationQueue;
//结果回调队列
@property (nonatomic, strong) dispatch_queue_t responseQueue;
//最大活跃任务数
@property (nonatomic, assign) NSInteger maximumActiveDownloads;
//当前活跃任务数
@property (nonatomic, assign) NSInteger activeRequestCount;
//排队中的任务数组(activeRequestCount > maximumActiveDownloads)任务需要排队
@property (nonatomic, strong) NSMutableArray *queuedMergedTasks;
//保存请求合并回调回调对象,用于结果回调
@property (nonatomic, strong) NSMutableDictionary *mergedTasks;
  • synchronizationQueue:全局串行队里,用于执行依赖任务;
  • responseQueue:全局并发队列,用于高效执行结果回调;
  • maximumActiveDownloads:允许的最大并发下载数量.在AFN中设定最大并发数量为4;
  • activeRequestCount:当前正在并发的下载任务数量;
  • queuedMergedTasks:当需要下载的任务超过maximumActiveDownloads时,未能执行的任务需要保存在该数组中进行排队;
  • mergedTasks:用于保存请求对应的合并回调对象.当获取到请求结果时,通过资源链接获取到保存在该数组中的对应的AFImageDownloaderMergedTask对象进行结果回调.

2.4.1 defaultURLCache

+ (NSURLCache *)defaultURLCache {
    
    // It's been discovered that a crash will occur on certain versions
    // of iOS if you customize the cache.
    //
    // More info can be found here: https://devforums.apple.com/message/1102182#1102182
    //
    // When iOS 7 support is dropped, this should be modified to use
    // NSProcessInfo methods instead.
    if ([[[UIDevice currentDevice] systemVersion] compare:@"8.2" options:NSNumericSearch] == NSOrderedAscending) {
        return [NSURLCache sharedURLCache];
    }
//默认设定内存缓存为20M,硬盘缓存为150M
    return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
                                         diskCapacity:150 * 1024 * 1024
                                             diskPath:@"com.alamofire.imagedownloader"];
}

AFImageDownloader使用了系统NSURLCache进行请求的缓存,这也是AFN曾经和SD撕扯的一个技术点,该类会对请求进行本地数据的持久化,文章最后我们再做统一说明.而之所以要区分系统个是因为在低版本的系统上使用自定的NSURLCache会有闪退,当iOS 7的支持被摒弃之后,会使用NSProcessInfo来进行替换.

2.4.2 defaultURLSessionConfiguration

+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration {
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];

    //TODO set the default HTTP headers
//允许设置cookie信息
    configuration.HTTPShouldSetCookies = YES;
//不允许http请求在没收到回复之前再次发情请求:由于http请求没有状态,所以客户端依赖发起顺序处理结果
    configuration.HTTPShouldUsePipelining = NO;
//设置缓存策略
    configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
//允许通过CellularAccess发起链接请求
    configuration.allowsCellularAccess = YES;
//设置超时时间
    configuration.timeoutIntervalForRequest = 60.0;
//使用自定义NSURLCache缓存
    configuration.URLCache = [AFImageDownloader defaultURLCache];

    return configuration;
}

该方法用于生成默认使用的NSURLSessionConfiguration对象.

2.4.3 init

//AFImageDownloader默认的单例实现
+ (instancetype)defaultInstance {
    static AFImageDownloader *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}
//AFImageDownloader默认的init实现
- (instancetype)init {
    NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration];
    return [self initWithSessionConfiguration:defaultConfiguration];
}

//接收外界实现自定义NSURLSessionConfiguration
- (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;

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

AFImageDownloader提供了系统默认的init实现,同时开放了给多的自定义实现接口给用户使用.

2.4.4  downloadImageForURLRequest:withReceiptID:success:failure:

我们将该方法拆分为两部分,发起请求之前的处理和获取到请求结果之后的处理.

- (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 {
//    1.判断当前资源链接是否为空
    __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;
        }
//        2. 如果资源链接对应的AFImageDownloaderMergedTask已经存在(说明该链接对应的资源请求已经发出但是还没有收到请求结果)则将该回调添加到该对象中,同时直接返回,不再发起重复的请求
        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;
        }
        
        //3. 如果缓存策略允许从缓存中获取资源,则查看缓存中是否存在资源
        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;
        }

        
        //4. 创建资源请求,设置授权,检查可用性以及结果序列化解析器
        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) {
                           //这块代码我们单独分析
                       }];

//        5. 存储response handler用户请求完成时回调
        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;
        
//        6. 根据当前的活跃请求数,决定发起请求或者让请求排队
        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;
    }
}

这个是实现资源加载的主要方法:

  • 根据请求URLIdentifier判断该资源链接是否为空,如果为空则直接返回;
  • 根据URLIdentifier判断在mergedTasks是否已经存在对应的mergedTask对象,如果存在则直接将reponse handler添加到该对象mergedTask中,直接返回,不再针对同一资源发起重复请求;
  • 根据request的cachePolicy策略来判断是否要从缓存中查找资源:如果当前请求的缓存策略允许从内存中获取则直接从内存中获取,并返回;
  • 经过上述条件过滤,则证明需要需要发起资源请求:创建资源请求,设置授权,检查可用性以及结果序列化解析器;
  • 创建mergedTask,并将当前请求对应的reponse handler添加到mergedTask,保存至全局mergedTasks对象;
  • 处理当前mergedTask:根据activeRequestCount是否小于maximumActiveDownloads来确定是直接发起资源请求还是让请求暂时排队.其中排队中的任务会在请求完成之后重新开始.

当收到请求结果之后:

                           dispatch_async(self.responseQueue, ^{
                               //遍历mergedTask并执行response handler回调结果
                               __strong __typeof__(weakSelf) strongSelf = weakSelf;
                               AFImageDownloaderMergedTask *mergedTask = strongSelf.mergedTasks[URLIdentifier];
                               if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
                                   //移除当前的URLIdentifier对应的mergedTask对象
                                   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);
                                               });
                                           }
                                       }
                                       
                                   }
                               }
                               //当前活跃请求数activeRequestCount减一
                               [strongSelf safelyDecrementActiveTaskCount];
                               //如果有请求在排队则开启新的
                               [strongSelf safelyStartNextTaskIfNecessary];
                           });

收到请求结果之后:

  • 根据URLIdentifier获取到对应的mergedTask,mergedTask保存了针对该URLIdentifier的所有请求回调,同时删除mergedTasks中的mergedTask引用;
  • 请求出现错误:如果请求过程总出现错误,则遍历mergedTask中保存的失败回调,并逐个调用;
  • 正常返回请求资源:如果请求过程中无异常,首先判断是否需要缓存图片:根据shouldCacheImage:forRequest:withAdditionalIdentifier:确定该资源是否需要缓存(默认实现需要缓存);然后遍历mergedTask中保存的成功回调,并逐个调用;
  • 将当前活跃的activeRequestCount减一,并唤醒一个新的排队任务.

2.4.5  cancelTaskForImageDownloadReceipt:

/**
 取消指定请求

 @param imageDownloadReceipt 请求对应的AFImageDownloadReceipt对象
 */
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
    dispatch_sync(self.synchronizationQueue, ^{
        NSString *URLIdentifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString;
        //1. 获取到对应的AFImageDownloaderMergedTask对象
        AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
        //2. 获取到对应的handler在mergedTask中的index
        NSUInteger index = [mergedTask.responseHandlers indexOfObjectPassingTest:^BOOL(AFImageDownloaderResponseHandler * _Nonnull handler, __unused NSUInteger idx, __unused BOOL * _Nonnull stop) {
            return handler.uuid == imageDownloadReceipt.receiptID;
        }];

        
        if (index != NSNotFound) {
        /*
         如果查询到对应的handler:
         1. 获取到handler的引用;
         2. 从mergedTask移除该回调;
         3. 执行h该回调
         */
            AFImageDownloaderResponseHandler *handler = mergedTask.responseHandlers[index];
            [mergedTask removeResponseHandler:handler];
            NSString *failureReason = [NSString stringWithFormat:@"ImageDownloader cancelled URL request: %@",imageDownloadReceipt.task.originalRequest.URL.absoluteString];
            NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey:failureReason};
            NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo];
            if (handler.failureBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    handler.failureBlock(imageDownloadReceipt.task.originalRequest, nil, error);
                });
            }
        }
        //如果对应的mergedTask已经没有其他回调,且对应的task尚未开始执行则取消该任务,并从排队队列中移除
        if (mergedTask.responseHandlers.count == 0 && mergedTask.task.state == NSURLSessionTaskStateSuspended) {
            [mergedTask.task cancel];
            [self removeMergedTaskWithURLIdentifier:URLIdentifier];
        }
    });
}

这个方法主要用来处理取消任务:

  • 根据URLIdentifier获取到对应的mergedTask对象;
  • 获取该imageDownloadReceipt对应的handler回调在mergedTask.responseHandlers索引;
  • 如果获取索引成功:首先获取索引到对应的handler引用;然后从mergedTask删除该handler引用;最后执行该handler失败回调.
缓存策略 typedef NS_ENUM(NSUInteger, YBCacheType){ YBCacheTypeReturnCacheDataThenLoad = 0,///< 有缓存就先返回缓存,同步请求数据 YBCacheTypeReloadIgnoringLocalCacheData, ///< 忽略缓存,重新请求 YBCacheTypeReturnCacheDataElseLoad,///< 有缓存就用缓存,没有缓存就重新请求(用于数据不变时) YBCacheTypeReturnCacheDataDontLoad,///< 有缓存就用缓存,没有缓存就不发请求,当做请求出错处理(用于离线模式) YBCacheTypeReturnCacheDataExpireThenLoad///< 有缓存就用缓存,如果过期了就重新请求 没过期就不请求 }; //get请求 - (void)test_get { NSString *url = @"http://www.baidu.com"; NSDictionary *params = @{@"i":@"1"}; [YBHttpTool get:url params:params success:^(NSDictionary *obj) { //成功 } failure:^(NSError *error) { //失败 }]; } //get带缓存的请求 - (void)test_get_cache { NSString *url = @"http://www.baidu.com"; NSDictionary *params = @{@"i":@"1"}; [YBHttpTool get:url params:params cacheType:YBCacheTypeReturnCacheDataThenLoad success:^(NSDictionary *obj) { //成功 } failure:^(NSError *error) { //失败 }]; } //post请求带缓存 - (void)test_post_cache { NSString *url = @"http://www.baidu.com"; NSDictionary *params = @{@"i":@"1"}; [YBHttpTool post:url params:params cacheType:YBCacheTypeReturnCacheDataThenLoad success:^(NSDictionary *obj) { //成功 } failure:^(NSError *error) { //失败 }]; } //上传单张图片 - (void)test_upload_image { UIImage *image = [UIImage imageNamed:@"1"]; NSData *data = UIImageJPEGRepresentation(image, 0.5); [YBHttpTool uploadImageWithImage:data success:^(NSDictionary *obj) { //上传成功 } failure:^(NSError *error) { //上传失败 }]; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值