AFNetworking之UIKit扩展与缓存实现
来源:互联网 发布:灯杆数据基础调研 编辑:程序博客网 时间:2024/06/08 11:36
写在开头:
- 大概回忆下,之前我们讲了
AFNetworking
整个网络请求的流程,包括request
的拼接,session
代理的转发,response
的解析。以及对一些bug
的适配,如果你还没有看过,可以点这里:
AFNetworking到底做了什么?
AFNetworking到底做了什么(二)? - 除此之外我们还单独的开了一篇讲了AF对
https
的处理:
AFNetworking之于https认证 - 本文将涉及部分AF对UIKit的扩展与图片下载相关缓存的实现,文章内容相对独立,如果没看过前文,也不影响阅读。
回到正文:
我们来看看AF对UIkit
的扩展:
一共如上这个多类,下面我们开始着重讲其中两个UIKit的扩展:
- 一个是我们网络请求时状态栏的小菊花。
- 一个是我们几乎都用到过请求网络图片的如下一行方法:
- (void)setImageWithURL:(NSURL *)url ;
我们开始吧:
1.AFNetworkActivityIndicatorManager
这个类的作用相当简单,就是当网络请求的时候,状态栏上的小菊花就会开始转:
需要的代码也很简单,只需在你需要它的位置中(比如AppDelegate)导入类,并加一行代码即可:
#import "AFNetworkActivityIndicatorManager.h"
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
接下来我们来讲讲这个类的实现:
这个类的实现也非常简单,还记得我们之前讲的AF对
NSURLSessionTask
中做了一个Method Swizzling吗?大意是把它的resume
和suspend
方法做了一个替换,在原有实现的基础上添加了一个通知的发送。这个类就是基于这两个通知和task完成的通知来实现的。
首先我们来看看它的初始化方法:
+ (instancetype)sharedManager { static AFNetworkActivityIndicatorManager *_sharedManager = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _sharedManager = [[self alloc] init]; }); return _sharedManager;}- (instancetype)init { self = [super init]; if (!self) { return nil; } //设置状态为没有request活跃 self.currentState = AFNetworkActivityManagerStateNotActive; //开始下载通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingTaskDidResumeNotification object:nil]; //挂起通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidSuspendNotification object:nil]; //完成通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidCompleteNotification object:nil]; //开始延迟 self.activationDelay = kDefaultAFNetworkActivityManagerActivationDelay; //结束延迟 self.completionDelay = kDefaultAFNetworkActivityManagerCompletionDelay; return self;}
初始化如上,设置了一个state,这个state是一个枚举:
typedef NS_ENUM(NSInteger, AFNetworkActivityManagerState) { //没有请求 AFNetworkActivityManagerStateNotActive, //请求延迟开始 AFNetworkActivityManagerStateDelayingStart, //请求进行中 AFNetworkActivityManagerStateActive, //请求延迟结束 AFNetworkActivityManagerStateDelayingEnd};
这个state一共如上4种状态,其中两种应该很好理解,而延迟开始和延迟结束怎么理解呢?
- 原来这是AF对请求菊花显示做的一个优化处理,试问如果一个请求时间很短,那么菊花很可能闪一下就结束了。如果很多请求过来,那么菊花会不停的闪啊闪,这显然并不是我们想要的效果。
- 所以多了这两个参数:
1)在一个请求开始的时候,我延迟一会在去转菊花,如果在这延迟时间内,请求结束了,那么我就不需要去转菊花了。
2)但是一旦转菊花开始,哪怕很短请求就结束了,我们还是会去转一个时间再去结束,这时间就是延迟结束的时间。
紧接着我们监听了三个通知,用来监听当前正在进行的网络请求的状态。
然后设置了我们前面提到的这个转菊花延迟开始和延迟结束的时间,这两个默认值如下:
static NSTimeInterval const kDefaultAFNetworkActivityManagerActivationDelay = 1.0;static NSTimeInterval const kDefaultAFNetworkActivityManagerCompletionDelay = 0.17;
接着我们来看看三个通知触发调用的方法:
//请求开始- (void)networkRequestDidStart:(NSNotification *)notification { if ([AFNetworkRequestFromNotification(notification) URL]) { //增加请求活跃数 [self incrementActivityCount]; }}//请求结束- (void)networkRequestDidFinish:(NSNotification *)notification { //AFNetworkRequestFromNotification(notification)返回这个通知的request,用来判断request是否是有效的 if ([AFNetworkRequestFromNotification(notification) URL]) { //减少请求活跃数 [self decrementActivityCount]; }}
方法很简单,就是开始的时候增加了请求活跃数,结束则减少。调用了如下两个方法进行加减:
//增加请求活跃数- (void)incrementActivityCount { //活跃的网络数+1,并手动发送KVO [self willChangeValueForKey:@"activityCount"]; @synchronized(self) { _activityCount++; } [self didChangeValueForKey:@"activityCount"]; //主线程去做 dispatch_async(dispatch_get_main_queue(), ^{ [self updateCurrentStateForNetworkActivityChange]; });}//减少请求活跃数- (void)decrementActivityCount { [self willChangeValueForKey:@"activityCount"]; @synchronized(self) {#pragma clang diagnostic push#pragma clang diagnostic ignored "-Wgnu" _activityCount = MAX(_activityCount - 1, 0);#pragma clang diagnostic pop } [self didChangeValueForKey:@"activityCount"]; dispatch_async(dispatch_get_main_queue(), ^{ [self updateCurrentStateForNetworkActivityChange]; });}
方法做了什么应该很容易看明白,这里需要注意的是,task的几个状态的通知,是会在多线程的环境下发送过来的。所以这里对活跃数的加减,都用了@synchronized
这种方式的锁,进行了线程保护。然后回到主线程调用了updateCurrentStateForNetworkActivityChange
我们接着来看看这个方法:
- (void)updateCurrentStateForNetworkActivityChange { //如果是允许小菊花 if (self.enabled) { switch (self.currentState) { //不活跃 case AFNetworkActivityManagerStateNotActive: //判断活跃数,大于0为YES if (self.isNetworkActivityOccurring) { //设置状态为延迟开始 [self setCurrentState:AFNetworkActivityManagerStateDelayingStart]; } break; case AFNetworkActivityManagerStateDelayingStart: //No op. Let the delay timer finish out. break; case AFNetworkActivityManagerStateActive: if (!self.isNetworkActivityOccurring) { [self setCurrentState:AFNetworkActivityManagerStateDelayingEnd]; } break; case AFNetworkActivityManagerStateDelayingEnd: if (self.isNetworkActivityOccurring) { [self setCurrentState:AFNetworkActivityManagerStateActive]; } break; } }}
- 这个方法先是判断了我们一开始设置是否需要菊花的
self.enabled
,如果需要,才执行。 - 这里主要是根据当前的状态,来判断下一个状态应该是什么。其中有这么一个属性
self.isNetworkActivityOccurring
:
那么这个方法应该不难理解了。//判断是否活跃- (BOOL)isNetworkActivityOccurring { @synchronized(self) { return self.activityCount > 0; }}
这个类复写了currentState的set方法,每当我们改变这个state,就会触发set方法,而怎么该转菊花也在该方法中:
//设置当前小菊花状态- (void)setCurrentState:(AFNetworkActivityManagerState)currentState { @synchronized(self) { if (_currentState != currentState) { //KVO [self willChangeValueForKey:@"currentState"]; _currentState = currentState; switch (currentState) { //如果为不活跃 case AFNetworkActivityManagerStateNotActive: //取消两个延迟用的timer [self cancelActivationDelayTimer]; [self cancelCompletionDelayTimer]; //设置小菊花不可见 [self setNetworkActivityIndicatorVisible:NO]; break; case AFNetworkActivityManagerStateDelayingStart: //开启一个定时器延迟去转菊花 [self startActivationDelayTimer]; break; //如果是活跃状态 case AFNetworkActivityManagerStateActive: //取消延迟完成的timer [self cancelCompletionDelayTimer]; //开始转菊花 [self setNetworkActivityIndicatorVisible:YES]; break; //延迟完成状态 case AFNetworkActivityManagerStateDelayingEnd: //开启延迟完成timer [self startCompletionDelayTimer]; break; } } [self didChangeValueForKey:@"currentState"]; }}
这个set方法就是这个类最核心的方法了。它的作用如下:
- 这里根据当前状态,是否需要开始执行一个延迟开始或者延迟完成,又或者是否需要取消这两个延迟。
还判断了,是否需要去转状态栏的菊花,调用了
setNetworkActivityIndicatorVisible:
方法:- (void)setNetworkActivityIndicatorVisible:(BOOL)networkActivityIndicatorVisible { if (_networkActivityIndicatorVisible != networkActivityIndicatorVisible) { [self willChangeValueForKey:@"networkActivityIndicatorVisible"]; @synchronized(self) { _networkActivityIndicatorVisible = networkActivityIndicatorVisible; } [self didChangeValueForKey:@"networkActivityIndicatorVisible"]; //支持自定义的Block,去自己控制小菊花 if (self.networkActivityActionBlock) { self.networkActivityActionBlock(networkActivityIndicatorVisible); } else { //否则默认AF根据该Bool,去控制状态栏小菊花是否显示 [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible]; } }}
- 这个方法就是用来控制菊花是否转。并且支持一个自定义的Block,我们可以自己去拿到这个菊花是否应该转的状态值,去做一些自定义的处理。
- 如果我们没有实现这个Block,则调用:
去转菊花。[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible];
回到state的set方法中,我们除了控制菊花去转,还调用了以下4个方法:
//开始任务到结束的时间,默认为1秒,如果1秒就结束,那么不转菊花,延迟去开始转- (void)startActivationDelayTimer { //只执行一次 self.activationDelayTimer = [NSTimer timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO]; //添加到主线程runloop去触发 [[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes];}//完成任务到下一个任务开始,默认为0.17秒,如果0.17秒就开始下一个,那么不停 延迟去结束菊花转- (void)startCompletionDelayTimer { //先取消之前的 [self.completionDelayTimer invalidate]; //延迟执行让菊花不在转 self.completionDelayTimer = [NSTimer timerWithTimeInterval:self.completionDelay target:self selector:@selector(completionDelayTimerFired) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:self.completionDelayTimer forMode:NSRunLoopCommonModes];}- (void)cancelActivationDelayTimer { [self.activationDelayTimer invalidate];}- (void)cancelCompletionDelayTimer { [self.completionDelayTimer invalidate];}
这4个方法分别是开始延迟执行一个方法,和结束的时候延迟执行一个方法,和对应这两个方法的取消。其作用,注释应该很容易理解。
我们继续往下看,这两个延迟调用的到底是什么:
- (void)activationDelayTimerFired { //活跃状态,即活跃数大于1才转 if (self.networkActivityOccurring) { [self setCurrentState:AFNetworkActivityManagerStateActive]; } else { [self setCurrentState:AFNetworkActivityManagerStateNotActive]; }}- (void)completionDelayTimerFired { [self setCurrentState:AFNetworkActivityManagerStateNotActive];}
一个开始,一个完成调用,都设置了不同的currentState的值,又回到之前state
的set
方法中了。
至此这个AFNetworkActivityIndicatorManager
类就讲完了,代码还是相当简单明了的。
2.UIImageView+AFNetworking
接下来我们来讲一个我们经常用的方法,这个方法的实现类是:UIImageView+AFNetworking.h
。
这是个类目,并且给UIImageView扩展了4个方法:
- (void)setImageWithURL:(NSURL *)url;- (void)setImageWithURL:(NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderImage;- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest placeholderImage:(nullable UIImage *)placeholderImage success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;- (void)cancelImageDownloadTask;
- 前两个想必不用我说了,没有谁没用过吧...就是给一个UIImageView去异步的请求一张图片,并且可以设置一张占位图。
- 第3个方法设置一张图,并且可以拿到成功和失败的回调。
- 第4个方法,可以取消当前的图片设置请求。
无论SDWebImage
,还是YYKit
,或者AF
,都实现了这么个类目。
AF关于这个类目UIImageView+AFNetworking
的实现,依赖于这么两个类:AFImageDownloader
,AFAutoPurgingImageCache
。
当然AFImageDownloader
中,关于图片数据请求的部分,还是使用AFURLSessionManager
来实现的。
接下来我们就来看看AFImageDownloader:
先看看初始化方法:
//该类为单例+ (instancetype)defaultInstance { static AFImageDownloader *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance;}- (instancetype)init { NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration]; AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:defaultConfiguration]; sessionManager.responseSerializer = [AFImageResponseSerializer serializer]; return [self initWithSessionManager:sessionManager downloadPrioritization:AFImageDownloadPrioritizationFIFO maximumActiveDownloads:4 imageCache:[[AFAutoPurgingImageCache alloc] init]];}+ (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;}
该类为单例,上述方法中,创建了一个sessionManager
,这个sessionManager
将用于我们之后的网络请求。从这里我们可以看到,这个类的网络请求都是基于之前AF自己封装的AFHTTPSessionManager
。
在这里初始化了一系列的对象,需要讲一下的是
AFImageDownloadPrioritizationFIFO
,这个一个枚举值:typedef NS_ENUM(NSInteger, AFImageDownloadPrioritization) { //先进先出 AFImageDownloadPrioritizationFIFO, //后进先出 AFImageDownloadPrioritizationLIFO};
这个枚举值代表着,一堆图片下载,执行任务的顺序。
还有一个
AFAutoPurgingImageCache
的创建,这个类是AF做图片缓存用的。这里我们暂时就这么理解它,讲完当前类,我们再来补充它。- 除此之外,我们还看到一个cache:
configuration.URLCache = [AFImageDownloader defaultURLCache];
大家看到这可能迷惑了,怎么这么多cache,那AF做图片缓存到底用哪个呢?答案是AF自己控制的图片缓存用//设置一个系统缓存,内存缓存为20M,磁盘缓存为150M,//这个是系统级别维护的缓存。+ (NSURLCache *)defaultURLCache { return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024 diskCapacity:150 * 1024 * 1024 diskPath:@"com.alamofire.imagedownloader"];}
AFAutoPurgingImageCache
,而NSUrlRequest
的缓存由它自己内部根据策略去控制,用的是NSURLCache
,不归AF处理,只需在configuration中设置上即可。- 那么看到这有些小伙伴又要问了,为什么不直接用
NSURLCache
,还要自定义一个AFAutoPurgingImageCache
呢?原来是因为NSURLCache
的诸多限制,例如只支持get请求等等。而且因为是系统维护的,我们自己的可控度不强,并且如果需要做一些自定义的缓存处理,无法实现。 - 更多关于
NSURLCache
的内容,大家可以自行查阅。
- 那么看到这有些小伙伴又要问了,为什么不直接用
接着上面的方法调用到这个最终的初始化方法中:
- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization maximumActiveDownloads:(NSInteger)maximumActiveDownloads imageCache:(id <AFImageRequestCache>)imageCache { if (self = [super init]) { //持有 self.sessionManager = sessionManager; //定义下载任务的顺序,默认FIFO,先进先出-队列模式,还有后进先出-栈模式 self.downloadPrioritizaton = downloadPrioritization; //最大的下载数 self.maximumActiveDownloads = maximumActiveDownloads; //自定义的cache self.imageCache = imageCache; //队列中的任务,待执行的 self.queuedMergedTasks = [[NSMutableArray alloc] init]; //合并的任务,所有任务的字典 self.mergedTasks = [[NSMutableDictionary alloc] init]; //活跃的request数 self.activeRequestCount = 0; //用UUID来拼接名字 NSString *name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.synchronizationqueue-%@", [[NSUUID UUID] UUIDString]]; //创建一个串行的queue self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL); name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]]; //创建并行queue self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT); } return self;}
这边初始化了一些属性,这些属性跟着注释看应该很容易明白其作用。主要需要注意的就是,这里创建了两个queue:一个串行的请求queue,和一个并行的响应queue。
- 这个串行queue,是用来做内部生成task等等一系列业务逻辑的。它保证了我们在这些逻辑处理中的线程安全问题(迷惑的接着往下看)。
- 这个并行queue,被用来做网络请求完成的数据回调。
接下来我们来看看它的创建请求task的方法:
- (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];}- (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 { //还是类似之前的,同步串行去做下载的事 生成一个task,这些事情都是在当前线程中串行同步做的,所以不用担心线程安全问题。 __block NSURLSessionDataTask *task = nil; dispatch_sync(self.synchronizationQueue, ^{ //url字符串 NSString *URLIdentifier = request.URL.absoluteString; if (URLIdentifier == nil) { if (failure) { //错误返回,没Url NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil]; dispatch_async(dispatch_get_main_queue(), ^{ failure(request, nil, error); }); } return; } //如果这个任务已经存在,则添加成功失败Block,然后直接返回,即一个url用一个request,可以响应好几个block //从自己task字典中根据Url去取AFImageDownloaderMergedTask,里面有task id url等等信息 AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier]; if (existingMergedTask != nil) { //里面包含成功和失败Block和UUid AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure]; //添加handler [existingMergedTask addResponseHandler:handler]; //给task赋值 task = existingMergedTask.task; return; } //根据request的缓存策略,加载缓存 switch (request.cachePolicy) { //这3种情况都会去加载缓存 case NSURLRequestUseProtocolCachePolicy: case NSURLRequestReturnCacheDataElseLoad: case NSURLRequestReturnCacheDataDontLoad: { //从cache中根据request拿数据 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,也没有cache,开始请求 NSUUID *mergedTaskIdentifier = [NSUUID UUID]; //task NSURLSessionDataTask *createdTask; __weak __typeof__(self) weakSelf = self; //用sessionManager的去请求,注意,只是创建task,还是挂起状态 createdTask = [self.sessionManager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { //在responseQueue中回调数据,初始化为并行queue dispatch_async(self.responseQueue, ^{ __strong __typeof__(weakSelf) strongSelf = weakSelf; //拿到当前的task AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier]; //如果之前的task数组中,有这个请求的任务task,则从数组中移除 if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) { //安全的移除,并返回当前被移除的AF task mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier]; //请求错误 if (error) { //去遍历task所有响应的处理 for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) { //主线程,调用失败的Block if (handler.failureBlock) { dispatch_async(dispatch_get_main_queue(), ^{ handler.failureBlock(request, (NSHTTPURLResponse*)response, error); }); } } } else { //成功根据request,往cache里添加 [strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil]; //调用成功Block 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 //创建handler AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure]; //创建task AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc] initWithURLIdentifier:URLIdentifier identifier:mergedTaskIdentifier task:createdTask]; //添加handler [mergedTask addResponseHandler:handler]; //往当前任务字典里添加任务 self.mergedTasks[URLIdentifier] = mergedTask; // 5) Either start the request or enqueue it depending on the current active request count //如果小于,则开始任务下载resume if ([self isActiveRequestCountBelowMaximumLimit]) { [self startMergedTask:mergedTask]; } else { [self enqueueMergedTask:mergedTask]; } //拿到最终生成的task task = mergedTask.task; }); if (task) { //创建一个AFImageDownloadReceipt并返回,里面就多一个receiptID。 return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task]; } else { return nil; }}
就这么一个非常非常长的方法,这个方法执行的内容都是在我们之前创建的串行queue中,同步的执行的,这是因为这个方法绝大多数的操作都是需要线程安全的。可以对着源码和注释来看,我们在这讲下它做了什么:
- 首先做了一个url的判断,如果为空则返回失败Block。
判断这个需要请求的url,是不是已经被生成的task中,如果是的话,则多添加一个回调处理就可以。回调处理对象为
AFImageDownloaderResponseHandler
。这个类非常简单,总共就如下3个属性:@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;}
当这个task完成的时候,会调用我们添加的回调。
关于
AFImageDownloaderMergedTask
,我们在这里都用的是这种类型的task,其实这个task也很简单:@interface AFImageDownloaderMergedTask : NSObject@property (nonatomic, strong) NSString *URLIdentifier;@property (nonatomic, strong) NSUUID *identifier;@property (nonatomic, strong) NSURLSessionDataTask *task;@property (nonatomic, strong) NSMutableArray <AFImageDownloaderResponseHandler*> *responseHandlers;@end@implementation AFImageDownloaderMergedTask- (instancetype)initWithURLIdentifier:(NSString *)URLIdentifier identifier:(NSUUID *)identifier task:(NSURLSessionDataTask *)task { if (self = [self init]) { self.URLIdentifier = URLIdentifier; self.task = task; self.identifier = identifier; self.responseHandlers = [[NSMutableArray alloc] init]; } return self;}//添加任务完成回调- (void)addResponseHandler:(AFImageDownloaderResponseHandler*)handler { [self.responseHandlers addObject:handler];}//移除任务完成回调- (void)removeResponseHandler:(AFImageDownloaderResponseHandler*)handler { [self.responseHandlers removeObject:handler];}@end
其实就是除了
NSURLSessionDataTask
,多加了几个参数,URLIdentifier
和identifier
都是用来标识这个task的,responseHandlers是用来存储task完成后的回调的,里面可以存一组,当任务完成时候,里面的回调都会被调用。- 接着去根据缓存策略,去加载缓存,如果有缓存,从
self.imageCache
中返回缓存,否则继续往下走。 - 走到这说明没相同url的task,也没有cache,那么就开始一个新的task,调用的是
AFUrlSessionManager
里的请求方法生成了一个task(这里我们就不赘述了,可以看之前的楼主之前的文章)。然后做了请求完成的处理。注意,这里处理实在我们一开始初始化的并行queue:self.responseQueue
中的,这里的响应处理是多线程并发进行的。
1)完成,则调用如下方法把这个task从全局字典中移除:
2)去循环这个task的//移除task相关,用同步串行的形式,防止移除中出现重复移除一系列问题- (AFImageDownloaderMergedTask*)safelyRemoveMergedTaskWithURLIdentifier:(NSString *)URLIdentifier { __block AFImageDownloaderMergedTask *mergedTask = nil; dispatch_sync(self.synchronizationQueue, ^{ mergedTask = [self removeMergedTaskWithURLIdentifier:URLIdentifier]; }); return mergedTask;}
responseHandlers
,调用它的成功或者失败的回调。
3)并且调用下面两个方法,去减少正在请求的任务数,和开启下一个任务:
这里需要注意的是,跟我们本类的一些数据相关的操作,都是在我们一开始的串行queue中同步进行的。//减少活跃的任务数- (void)safelyDecrementActiveTaskCount { //回到串行queue去- dispatch_sync(self.synchronizationQueue, ^{ if (self.activeRequestCount > 0) { self.activeRequestCount -= 1; } });}//如果可以,则开启下一个任务- (void)safelyStartNextTaskIfNecessary { //回到串行queue dispatch_sync(self.synchronizationQueue, ^{ //先判断并行数限制 if ([self isActiveRequestCountBelowMaximumLimit]) { while (self.queuedMergedTasks.count > 0) { //获取数组中第一个task AFImageDownloaderMergedTask *mergedTask = [self dequeueMergedTask]; //如果状态是挂起状态 if (mergedTask.task.state == NSURLSessionTaskStateSuspended) { [self startMergedTask:mergedTask]; break; } } } });}
4)除此之外,如果成功,还把成功请求到的数据,加到AF自定义的cache中://成功根据request,往cache里添加[strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
- 用
NSUUID
生成的唯一标识,去生成AFImageDownloaderResponseHandler
,然后生成一个AFImageDownloaderMergedTask
,把之前第5步生成的createdTask
和回调都绑定给这个AF自定义可合并回调的task,然后这个task加到全局的task映射字典中,key为url:self.mergedTasks[URLIdentifier] = mergedTask;
判断当前正在下载的任务是否超过最大并行数,如果没有则开始下载,否则先加到等待的数组中去:
//如果小于最大并行数,则开始任务下载resumeif ([self isActiveRequestCountBelowMaximumLimit]) { [self startMergedTask:mergedTask];} else { [self enqueueMergedTask:mergedTask];}
//判断并行数限制- (BOOL)isActiveRequestCountBelowMaximumLimit { return self.activeRequestCount < self.maximumActiveDownloads;}
//开始下载- (void)startMergedTask:(AFImageDownloaderMergedTask *)mergedTask { [mergedTask.task resume]; //任务活跃数+1 ++self.activeRequestCount;}//把任务先加到数组里- (void)enqueueMergedTask:(AFImageDownloaderMergedTask *)mergedTask { switch (self.downloadPrioritizaton) { //先进先出 case AFImageDownloadPrioritizationFIFO: [self.queuedMergedTasks addObject:mergedTask]; break; //后进先出 case AFImageDownloadPrioritizationLIFO: [self.queuedMergedTasks insertObject:mergedTask atIndex:0]; break; }}
- 先判断并行数限制,如果小于最大限制,则开始下载,把当前活跃的request数量+1。
- 如果暂时不能下载,被加到等待下载的数组中去的话,会根据我们一开始设置的下载策略,是先进先出,还是后进先出,去插入这个下载任务。
最后判断这个mergeTask是否为空。不为空,我们生成了一个
AFImageDownloadReceipt
,绑定了一个UUID。否则为空返回nil:if (task) { //创建一个AFImageDownloadReceipt并返回,里面就多一个receiptID。 return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];} else { return nil;}
这个
AFImageDownloadReceipt
仅仅是多封装了一个UUID:@interface AFImageDownloadReceipt : NSObject@property (nonatomic, strong) NSURLSessionDataTask *task;@property (nonatomic, strong) NSUUID *receiptID;@end@implementation AFImageDownloadReceipt- (instancetype)initWithReceiptID:(NSUUID *)receiptID task:(NSURLSessionDataTask *)task { if (self = [self init]) { self.receiptID = receiptID; self.task = task; } return self;}
这么封装是为了标识每一个task,我们后面可以根据这个
AFImageDownloadReceipt
来对task做取消操作。
这个AFImageDownloader
中最核心的方法基本就讲完了,还剩下一些方法没讲,像前面讲到的task的取消的方法:
//根据AFImageDownloadReceipt来取消任务,即对应一个响应回调。- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt { dispatch_sync(self.synchronizationQueue, ^{ //拿到url NSString *URLIdentifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString; //根据url拿到task AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier]; //快速遍历查找某个下标,如果返回YES,则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) { //移除响应处理 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]; //并调用失败block,原因为取消 if (handler.failureBlock) { dispatch_async(dispatch_get_main_queue(), ^{ handler.failureBlock(imageDownloadReceipt.task.originalRequest, nil, error); }); } } //如果任务里的响应回调为空或者状态为挂起,则取消task,并且从字典中移除 if (mergedTask.responseHandlers.count == 0 && mergedTask.task.state == NSURLSessionTaskStateSuspended) { [mergedTask.task cancel]; [self removeMergedTaskWithURLIdentifier:URLIdentifier]; } });}
//根据URLIdentifier移除task- (AFImageDownloaderMergedTask *)removeMergedTaskWithURLIdentifier:(NSString *)URLIdentifier { AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier]; [self.mergedTasks removeObjectForKey:URLIdentifier]; return mergedTask;}
方法比较简单,大家自己看看就好。至此`AFImageDownloader
这个类讲完了。如果大家看的感觉比较绕,没关系,等到最后我们一起来总结一下,捋一捋。
我们之前讲到AFAutoPurgingImageCache
这个类略过去了,现在我们就来补充一下这个类的相关内容:
首先来讲讲这个类的作用,它是AF自定义用来做图片缓存的。我们来看看它的初始化方法:
- (instancetype)init { //默认为内存100M,后者为缓存溢出后保留的内存 return [self initWithMemoryCapacity:100 * 1024 * 1024 preferredMemoryCapacity:60 * 1024 * 1024];}- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity { if (self = [super init]) { //内存大小 self.memoryCapacity = memoryCapacity; self.preferredMemoryUsageAfterPurge = preferredMemoryCapacity; //cache的字典 self.cachedImages = [[NSMutableDictionary alloc] init]; NSString *queueName = [NSString stringWithFormat:@"com.alamofire.autopurgingimagecache-%@", [[NSUUID UUID] UUIDString]]; //并行的queue self.synchronizationQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT); //添加通知,收到内存警告的通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllImages) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; } return self;}
初始化方法很简单,总结一下:
- 声明了一个默认的内存缓存大小100M,还有一个意思是如果超出100M之后,我们去清除缓存,此时仍要保留的缓存大小60M。(如果还是不理解,可以看后文,源码中会讲到)
- 创建了一个并行queue,这个并行queue,这个类除了初始化以外,所有的方法都是在这个并行queue中调用的。
创建了一个cache字典,我们所有的缓存数据,都被保存在这个字典中,key为url,value为
AFCachedImage
。
关于这个AFCachedImage
,其实就是Image之外封装了几个关于这个缓存的参数,如下:@interface AFCachedImage : NSObject@property (nonatomic, strong) UIImage *image;@property (nonatomic, strong) NSString *identifier; //url标识@property (nonatomic, assign) UInt64 totalBytes; //总大小@property (nonatomic, strong) NSDate *lastAccessDate; //上次获取时间@property (nonatomic, assign) UInt64 currentMemoryUsage; //这个参数没被用到过@end@implementation AFCachedImage//初始化-(instancetype)initWithImage:(UIImage *)image identifier:(NSString *)identifier { if (self = [self init]) { self.image = image; self.identifier = identifier; CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale); CGFloat bytesPerPixel = 4.0; CGFloat bytesPerSize = imageSize.width * imageSize.height; self.totalBytes = (UInt64)bytesPerPixel * (UInt64)bytesPerSize; self.lastAccessDate = [NSDate date]; } return self;}//上次获取缓存的时间- (UIImage*)accessImage { self.lastAccessDate = [NSDate date]; return self.image;}
添加了一个通知,监听内存警告,当发成内存警告,调用该方法,移除所有的缓存,并且把当前缓存数置为0:
//移除所有图片- (BOOL)removeAllImages { __block BOOL removed = NO; dispatch_barrier_sync(self.synchronizationQueue, ^{ if (self.cachedImages.count > 0) { [self.cachedImages removeAllObjects]; self.currentMemoryUsage = 0; removed = YES; } }); return removed;}
注意这个类大量的使用了
dispatch_barrier_sync
与dispatch_barrier_async
,小伙伴们如果对这两个方法有任何疑惑,可以看看这篇文章:dispatch_barrier_async与dispatch_barrier_sync异同。
1)这里我们可以看到使用了dispatch_barrier_sync
,这里没有用锁,但是因为使用了dispatch_barrier_sync
,不仅同步了synchronizationQueue
队列,而且阻塞了当前线程,所以保证了里面执行代码的线程安全问题。
2)在这里其实使用锁也可以,但是AF在这的处理却是使用同步的机制来保证线程安全,或许这跟图片的加载缓存的使用场景,高频次有关系,在这里使用sync,并不需要在去开辟新的线程,浪费性能,只需要在原有线程,提交到synchronizationQueue
队列中,阻塞的执行即可。这样省去大量的开辟线程与使用锁带来的性能消耗。(当然这仅仅是我的一个猜测,有不同意见的朋友欢迎讨论~)- 在这里用了
dispatch_barrier_sync
,因为synchronizationQueue
是个并行queue,所以在这里不会出现死锁的问题。 - 关于保证线程安全的同时,同步还是异步,与性能方面的考量,可以参考这篇文章:Objc的底层并发API。
- 在这里用了
接着我们来看看这个类最核心的一个方法:
//添加image到cache里- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier { //用dispatch_barrier_async,来同步这个并行队列 dispatch_barrier_async(self.synchronizationQueue, ^{ //生成cache对象 AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier]; //去之前cache的字典里取 AFCachedImage *previousCachedImage = self.cachedImages[identifier]; //如果有被缓存过 if (previousCachedImage != nil) { //当前已经使用的内存大小减去图片的大小 self.currentMemoryUsage -= previousCachedImage.totalBytes; } //把新cache的image加上去 self.cachedImages[identifier] = cacheImage; //加上内存大小 self.currentMemoryUsage += cacheImage.totalBytes; }); //做缓存溢出的清除,清除的是早期的缓存 dispatch_barrier_async(self.synchronizationQueue, ^{ //如果使用的内存大于我们设置的内存容量 if (self.currentMemoryUsage > self.memoryCapacity) { //拿到使用内存 - 被清空后首选内存 = 需要被清除的内存 UInt64 bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge; //拿到所有缓存的数据 NSMutableArray <AFCachedImage*> *sortedImages = [NSMutableArray arrayWithArray:self.cachedImages.allValues]; //根据lastAccessDate排序 升序,越晚的越后面 NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastAccessDate" ascending:YES]; [sortedImages sortUsingDescriptors:@[sortDescriptor]]; UInt64 bytesPurged = 0; //移除早期的cache bytesToPurge大小 for (AFCachedImage *cachedImage in sortedImages) { [self.cachedImages removeObjectForKey:cachedImage.identifier]; bytesPurged += cachedImage.totalBytes; if (bytesPurged >= bytesToPurge) { break ; } } //减去被清掉的内存 self.currentMemoryUsage -= bytesPurged; } });}
看注释应该很容易明白,这个方法做了两件事:
- 设置缓存到字典里,并且把对应的缓存大小设置到当前已缓存的数量属性中。
- 判断是缓存超出了我们设置的最大缓存100M,如果是的话,则清除掉部分早时间的缓存,清除到缓存小于我们溢出后保留的内存60M以内。
当然在这里更需要说一说的是dispatch_barrier_async
,这里整个类都没有使用dispatch_async
,所以不存在是为了做一个栅栏,来同步上下文的线程。其实它在本类中的作用很简单,就是一个串行执行。
- 讲到这,小伙伴们又疑惑了,既然就是只是为了串行,那为什么我们不用一个串行queue就得了?非得用
dispatch_barrier_async
干嘛?其实小伙伴要是看的仔细,就明白了,上文我们说过,我们要用dispatch_barrier_sync
来保证线程安全。如果我们使用串行queue,那么线程是极其容易死锁的。
还有剩下的几个方法:
//根据id获取图片- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier { __block UIImage *image = nil; //用同步的方式获取,防止线程安全问题 dispatch_sync(self.synchronizationQueue, ^{ AFCachedImage *cachedImage = self.cachedImages[identifier]; //并且刷新获取的时间 image = [cachedImage accessImage]; }); return image;}//根据request和additionalIdentifier添加cache- (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier { [self addImage:image withIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];}//根据request和additionalIdentifier移除图片- (BOOL)removeImageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier { return [self removeImageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];}//根据request和additionalIdentifier获取图片- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier { return [self imageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];}//生成id的方式为Url字符串+additionalIdentifier- (NSString *)imageCacheKeyFromURLRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)additionalIdentifier { NSString *key = request.URL.absoluteString; if (additionalIdentifier != nil) { key = [key stringByAppendingString:additionalIdentifier]; } return key;}
这几个方法都很简单,大家自己看看就好了,就不赘述了。至此AFAutoPurgingImageCache
也讲完了,我们还是等到最后再来总结。
我们绕了一大圈,总算回到了UIImageView+AFNetworking
这个类,现在图片下载的方法,和缓存的方法都有了,实现这个类也是水到渠成的事了。
我们来看下面我们绝大多数人很熟悉的方法,看看它的实现:
- (void)setImageWithURL:(NSURL *)url { [self setImageWithURL:url placeholderImage:nil];}- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholderImage{ //设置head,可接受类型为image NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; [request addValue:@"image/*" forHTTPHeaderField:@"Accept"]; [self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];}
上述方法按顺序往下调用,第二个方法给head的Accept类型设置为Image。接着调用到第三个方法,也是这个类目唯一一个重要的方法:
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest placeholderImage:(UIImage *)placeholderImage success:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure{ //url为空,则取消 if ([urlRequest URL] == nil) { //取消task [self cancelImageDownloadTask]; //设置为占位图 self.image = placeholderImage; return; } //看看设置的当前的回调的request和需要请求的request是不是为同一个,是的话为重复调用,直接返回 if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){ return; } //开始请求前,先取消之前的task,即解绑回调 [self cancelImageDownloadTask]; //拿到downloader AFImageDownloader *downloader = [[self class] sharedImageDownloader]; //拿到cache id <AFImageRequestCache> imageCache = downloader.imageCache; //Use the image from the image cache if it exists UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil]; //去获取cachedImage if (cachedImage) { //有的话直接设置,并且置空回调 if (success) { success(urlRequest, nil, cachedImage); } else { self.image = cachedImage; } [self clearActiveDownloadInformation]; } else { //无缓存,如果有占位图,先设置 if (placeholderImage) { self.image = placeholderImage; } __weak __typeof(self)weakSelf = self; NSUUID *downloadID = [NSUUID UUID]; AFImageDownloadReceipt *receipt; //去下载,并得到一个receipt,可以用来取消回调 receipt = [downloader downloadImageForURLRequest:urlRequest withReceiptID:downloadID success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) { __strong __typeof(weakSelf)strongSelf = weakSelf; //判断receiptID和downloadID是否相同 成功回调,设置图片 if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) { if (success) { success(request, response, responseObject); } else if(responseObject) { strongSelf.image = responseObject; } //置空回调 [strongSelf clearActiveDownloadInformation]; } } failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) { __strong __typeof(weakSelf)strongSelf = weakSelf; //失败有failuerBlock就回调, if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) { if (failure) { failure(request, response, error); } //置空回调对象 [strongSelf clearActiveDownloadInformation]; } }]; //赋值 self.af_activeImageDownloadReceipt = receipt; }}
这个方法,细节的地方可以关注注释,这里总结一下做了什么:
1)去判断url是否为空,如果为空则取消task,调用如下方法:
//取消task- (void)cancelImageDownloadTask { if (self.af_activeImageDownloadReceipt != nil) { //取消事件回调响应 [[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt]; //置空 [self clearActiveDownloadInformation]; }}//置空- (void)clearActiveDownloadInformation { self.af_activeImageDownloadReceipt = nil;}
- 这里注意
cancelImageDownloadTask
中,调用了self.af_activeImageDownloadReceipt
这么一个属性,看看定义的地方:
我们现在是给@interface UIImageView (_AFNetworking)@property (readwrite, nonatomic, strong, setter = af_setActiveImageDownloadReceipt:) AFImageDownloadReceipt *af_activeImageDownloadReceipt;@end@implementation UIImageView (_AFNetworking)//绑定属性 AFImageDownloadReceipt,就是一个事件响应的接受对象,包含一个task,一个uuid- (AFImageDownloadReceipt *)af_activeImageDownloadReceipt { return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, @selector(af_activeImageDownloadReceipt));}//set- (void)af_setActiveImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt { objc_setAssociatedObject(self, @selector(af_activeImageDownloadReceipt), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}@end
UIImageView
添加的一个类目,所以我们无法直接添加属性,而是使用的是runtime的方式来生成set和get方法生成了一个AFImageDownloadReceipt
类型的属性。看过上文应该知道这个对象里面就一个task和一个UUID。这个属性就是我们这次下载任务相关联的信息。
2)然后做了一系列判断,见注释。
3)然后生成了一个我们之前分析过得AFImageDownloader
,然后去获取缓存,如果有缓存,则直接读缓存。还记得AFImageDownloader
里也有一个读缓存的方法么?那个是和cachePolicy相关的,而这个是有缓存的话直接读取。不明白的可以回过头去看看。
4)走到这说明没缓存了,然后就去用AFImageDownloader
,我们之前讲过的方法,去请求图片。完成后,则调用成功或者失败的回调,并且置空属性self.af_activeImageDownloadReceipt
,成功则设置图片。
除此之外还有一个取消这次任务的方法:
//取消task- (void)cancelImageDownloadTask { if (self.af_activeImageDownloadReceipt != nil) { //取消事件回调响应 [[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt]; //置空 [self clearActiveDownloadInformation]; }}
其实也是去调用我们之前讲过的AFImageDownloader
的取消方法。
这个类总共就这么几行代码,就完成了我们几乎没有人不用的,设置ImageView图片的方法。当然真正的难点在于AFImageDownloader
和AFAutoPurgingImageCache
。
接下来我们来总结一下整个请求图片,缓存,然后设置图片的流程:
- 调用
- (void)setImageWithURL:(NSURL *)url;
时,我们生成AFImageDownloader
单例,并替我们请求数据。 - 而
AFImageDownloader
会生成一个AFAutoPurgingImageCache
替我们缓存生成的数据。当然我们设置的时候,给session
的configuration
设置了一个系统级别的缓存NSUrlCache
,这两者是互相独立工作的,互不影响的。 - 然后
AFImageDownloader
,就实现下载和协调AFAutoPurgingImageCache
去缓存,还有一些取消下载的方法。然后通过回调把数据给到我们的类目UIImageView+AFNetworking
,如果成功获取数据,则由类目设置上图片,整个流程结束。
经过这三个文件:UIImageView+AFNetworking
、AFImageDownloader
、AFAutoPurgingImageCache
,至此整个设置网络图片的方法结束了。
写在最后:
对于UIKit的总结,我们就到此为止了,其它部分的扩展,小伙伴们可以自行阅读,都很简单,基本上每个类200行左右的代码。核心功能基本上都是围绕
AFURLSessionManager
实现的。本来想本篇放在三里面完结,想想还是觉得自己...too young too simple...
但是下一篇应该是一个结束了,我们会讲讲AF2.x,然后详细总结一下AF存在的意义。大家任何有疑问或者不同意见的,欢迎评论,楼主会一一回复的。求关注,求赞
- AFNetworking之UIKit扩展与缓存实现
- AFNetworking之UIKit扩展与缓存实现
- AFNetworking 与 UIKit+AFNetworking 详解
- AFNetworking 与 UIKit+AFNetworking 详解
- AFNetworking 与 UIKit+AFNetworking 详解
- APC扩展缓存实现
- AFNetworking最新版本与NSFileManager自制缓存
- AFNetworking缓存
- Android进阶之缓存机制与实现
- iOS疯狂详解之AFNetworking图片缓存问题
- iOS之AFNetworking实现原理和使用方法
- iOS之AFNetworking实现原理和使用方法
- AFNetworking使用扩展
- AFNetworking忽略缓存
- AFNetworking图片缓存问题
- AFNetworking图片缓存问题
- iOS AFNetworking 数据缓存
- AFNetWorking缓存处理
- 初级贪吃蛇-霍氏贪吃蛇1.2版
- leetcode412. Fizz Buzz
- [编程题] 保卫方案
- CentOS7系统之间设置共享文件夹
- iOS开发之特征变量(Use Trait Variations)
- AFNetworking之UIKit扩展与缓存实现
- spring boot 输出简单的hello world(没用分层结构)
- Codeforces 845 Driving Test(模拟)
- Problem 2253 Salty Fish
- 地址总线、字长和内存空间的关系
- 关于PCB布线的顺序到底是怎样才合理?
- HDU 6053 TrickGCD(莫比乌斯反演+前缀和)
- 8张图理解Java
- SQL性能优化