AFNetworking之UIKit扩展与缓存实现

作者: 涂耀辉 | 来源:发表于2016-12-13 17:20 被阅读5895次
    写在开头:
    • 大概回忆下,之前我们讲了AFNetworking整个网络请求的流程,包括request的拼接,session代理的转发,response的解析。以及对一些bug的适配,如果你还没有看过,可以点这里:
      AFNetworking到底做了什么?
      AFNetworking到底做了什么(二)?
    • 除此之外我们还单独的开了一篇讲了AF对https的处理:
      AFNetworking之于https认证
    • 本文将涉及部分AF对UIKit的扩展与图片下载相关缓存的实现,文章内容相对独立,如果没看过前文,也不影响阅读。
    回到正文:

    我们来看看AF对UIkit的扩展:

    UIKit扩展.png
    一共如上这个多类,下面我们开始着重讲其中两个UIKit的扩展:
    • 一个是我们网络请求时状态栏的小菊花。
    • 一个是我们几乎都用到过请求网络图片的如下一行方法:
     - (void)setImageWithURL:(NSURL *)url ;
    
    我们开始吧:
    1.AFNetworkActivityIndicatorManager

    这个类的作用相当简单,就是当网络请求的时候,状态栏上的小菊花就会开始转:


    小菊花.png

    需要的代码也很简单,只需在你需要它的位置中(比如AppDelegate)导入类,并加一行代码即可:

    #import "AFNetworkActivityIndicatorManager.h"
    
    [[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
    
    接下来我们来讲讲这个类的实现:
    • 这个类的实现也非常简单,还记得我们之前讲的AF对NSURLSessionTask中做了一个Method Swizzling吗?大意是把它的resumesuspend方法做了一个替换,在原有实现的基础上添加了一个通知的发送。

    • 这个类就是基于这两个通知和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```类就讲完了,代码还是相当简单明了的。
    
    ![分割图.png](https://img.haomeiwen.com/i2702646/426da66cf4d16567.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    ######2.UIImageView+AFNetworking
    接下来我们来讲一个我们经常用的方法,这个方法的实现类是:```UIImageView+AFNetworking.h```。
    这是个类目,并且给UIImageView扩展了4个方法:
    
    • (void)setImageWithURL:(NSURL *)url;

    • (void)setImageWithURL:(NSURL *)url
      placeholderImage:(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];

    //设置一个系统缓存,内存缓存为20M,磁盘缓存为150M,
    //这个是系统级别维护的缓存。

    • (NSURLCache *)defaultURLCache {
      return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
      diskCapacity:150 * 1024 * 1024
      diskPath:@"com.alamofire.imagedownloader"];
      }
    大家看到这可能迷惑了,怎么这么多cache,那AF做图片缓存到底用哪个呢?答案是AF自己控制的图片缓存用```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中,同步的执行的,这是因为这个方法绝大多数的操作都是需要线程安全的。可以对着源码和注释来看,我们在这讲下它做了什么:
    1. 首先做了一个url的判断,如果为空则返回失败Block。
    2. 判断这个需要请求的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完成的时候,会调用我们添加的回调。
    
    3. 关于```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完成后的回调的,里面可以存一组,当任务完成时候,里面的回调都会被调用。
    4. 接着去根据缓存策略,去加载缓存,如果有缓存,从```self.imageCache```中返回缓存,否则继续往下走。
    5. 走到这说明没相同url的task,也没有cache,那么就开始一个新的task,调用的是```AFUrlSessionManager```里的请求方法生成了一个task(这里我们就不赘述了,可以看之前的楼主之前的文章)。然后做了请求完成的处理。注意,这里处理实在我们一开始初始化的并行queue:```self.responseQueue```中的,这里的响应处理是多线程并发进行的。
    1)完成,则调用如下方法把这个task从全局字典中移除:
    

    //移除task相关,用同步串行的形式,防止移除中出现重复移除一系列问题

    • (AFImageDownloaderMergedTask*)safelyRemoveMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
      __block AFImageDownloaderMergedTask *mergedTask = nil;
      dispatch_sync(self.synchronizationQueue, ^{
      mergedTask = [self removeMergedTaskWithURLIdentifier:URLIdentifier];
      });
      return mergedTask;
      }2)去循环这个task的responseHandlers```,调用它的成功或者失败的回调。
      3)并且调用下面两个方法,去减少正在请求的任务数,和开启下一个任务:
    //减少活跃的任务数
     - (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;
                    }
                }
            }
        });
    }
    

    这里需要注意的是,跟我们本类的一些数据相关的操作,都是在我们一开始的串行queue中同步进行的。
    4)除此之外,如果成功,还把成功请求到的数据,加到AF自定义的cache中:

    //成功根据request,往cache里添加
    [strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
    
    1. NSUUID生成的唯一标识,去生成AFImageDownloaderResponseHandler,然后生成一个AFImageDownloaderMergedTask,把之前第5步生成的createdTask和回调都绑定给这个AF自定义可合并回调的task,然后这个task加到全局的task映射字典中,key为url:
    self.mergedTasks[URLIdentifier] = mergedTask;
    
    1. 判断当前正在下载的任务是否超过最大并行数,如果没有则开始下载,否则先加到等待的数组中去:
    //如果小于最大并行数,则开始任务下载resume
    if ([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。
    • 如果暂时不能下载,被加到等待下载的数组中去的话,会根据我们一开始设置的下载策略,是先进先出,还是后进先出,去插入这个下载任务。
    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``这个类讲完了。如果大家看的感觉比较绕,没关系,等到最后我们一起来总结一下,捋一捋。

    分割图.png

    我们之前讲到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;
    }
    

    初始化方法很简单,总结一下:

    1. 声明了一个默认的内存缓存大小100M,还有一个意思是如果超出100M之后,我们去清除缓存,此时仍要保留的缓存大小60M。(如果还是不理解,可以看后文,源码中会讲到)
    2. 创建了一个并行queue,这个并行queue,这个类除了初始化以外,所有的方法都是在这个并行queue中调用的。
    3. 创建了一个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;
    }
    
    1. 添加了一个通知,监听内存警告,当发成内存警告,调用该方法,移除所有的缓存,并且把当前缓存数置为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_syncdispatch_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;
            }
        });
    }
    

    看注释应该很容易明白,这个方法做了两件事:

    1. 设置缓存到字典里,并且把对应的缓存大小设置到当前已缓存的数量属性中。
    2. 判断是缓存超出了我们设置的最大缓存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也讲完了,我们还是等到最后再来总结。

    分割图.png

    我们绕了一大圈,总算回到了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图片的方法。当然真正的难点在于AFImageDownloaderAFAutoPurgingImageCache

    接下来我们来总结一下整个请求图片,缓存,然后设置图片的流程:
    • 调用- (void)setImageWithURL:(NSURL *)url;时,我们生成
      AFImageDownloader单例,并替我们请求数据。
    • AFImageDownloader会生成一个AFAutoPurgingImageCache替我们缓存生成的数据。当然我们设置的时候,给sessionconfiguration设置了一个系统级别的缓存NSUrlCache,这两者是互相独立工作的,互不影响的。
    • 然后AFImageDownloader,就实现下载和协调AFAutoPurgingImageCache去缓存,还有一些取消下载的方法。然后通过回调把数据给到我们的类目UIImageView+AFNetworking,如果成功获取数据,则由类目设置上图片,整个流程结束。

    经过这三个文件:
    UIImageView+AFNetworkingAFImageDownloaderAFAutoPurgingImageCache,至此整个设置网络图片的方法结束了。

    写在最后:
    • 对于UIKit的总结,我们就到此为止了,其它部分的扩展,小伙伴们可以自行阅读,都很简单,基本上每个类200行左右的代码。核心功能基本上都是围绕AFURLSessionManager实现的。

    • 本来想本篇放在三里面完结,想想还是觉得自己...too young too simple...
      但是下一篇应该是一个结束了,我们会讲讲AF2.x,然后详细总结一下AF存在的意义。大家任何有疑问或者不同意见的,欢迎评论,楼主会一一回复的。求关注,求赞👍。感谢~~

    后续文章:

    AFNetworking到底做了什么?(终)

    相关文章

      网友评论

      • _阿南_:这个与SDWebImage比较,那个的性能会更加好,或者设计更加的巧妙?
      • _阿南_:一个开始,一个完成调用,都设置了不同的currentState的值,又回到之前

        楼主 这块的代码和文字乱了。
      • NotFunGuy:楼主,中间有一部分markdown格式乱了
      • 一代骄马:终于看完了 :disappointed_relieved:
      • 尼古拉斯骄傲:最强AFN总结 没有之一 要学习的太多了
      • Civel_Xu:UIImageView+AFNetworking 和 SDWebImage在使用图片加载上 效率上面有什么差距吗?
      • 淘代码者: dispatch_sync(self.synchronizationQueue, ^{
        NSString *URLIdentifier = request.URL.absoluteString;
        }
        这段代码本身就在当前线程中同步执行的,为什么还要用个串行队列同步执行一次呢?这里好困惑!
        a75e29cfc6f0:因为是多线程 执行 多线程同步执行 不保证 同步
      • 73b35c9fab45:谢谢楼主的文章!想请教一个问题,为啥要手动触发KVO呢?[self willChangeValueForKey:@"activityCount"];而且也没有实现automaticallyNotifiesObserversForKey方法?
      • Zeroxhj:hi configuration.URLCache这个session的系统缓存如果不设置的话,系统就不会进行缓存。这对我们图片自己的缓存没有影响啊。同时,由于这个URLCache的存在还占用了一部分的memoryCache,所以不太明白为什么要这么做
      • NSBug:花了周末看了一遍,收货很多,希望过一段时间在看一遍,有更多的收货
      • _没心没肺能活百岁_:请问楼主,afnetworking在文件下载的时候是断点续传的么,还是这部分逻辑需要自己去实现?
        涂耀辉:@清风飒月 当然有啊,downloadTaskWithResumeData:这个方法,成功恢复任务后会触发代理,如果失败会触发失败的代理
      • _没心没肺能活百岁_:楼主的总结太强大了,既有源码分析,又有基础知识补充,还有实际操作,牛!
      • iOS小虫下米:要用dispatch_barrier_sync来保证线程安全。如果我们使用串行queue,那么线程是极其容易死锁的。为什么AFNetworkActivityIndicatorManager类里的,就不会死锁呢???? :smile: 楼主很棒
        琦思妙想君:@yangye2016 ,我感觉作者的意思是容易隐蔽的死锁。
        如果在并行队列中用 dispatch_barrier_sync 要死锁的话,得两层嵌套在一起,这样比较容易发现。
        但是在串行队列中 queue 本身就是一个资源争夺者,在 queue 的某个任务中调用 dispatch_barrier_sync,或者在 dispatch_barrier_sync 的 block 中同步的向 queue 中添加任务,都会造成死锁,比较隐蔽
        yangye_2016:@涂耀辉 他上面这种我用代码试过了,换上串行队列也不会死锁
        涂耀辉:@iOS小虫下米 建议先了解了解死锁发生的条件。。。 :smiley:
      • 笨驴爱吃胡萝卜:代码分析的比较乱,不过总结的不错。最好有个uml图更好
        涂耀辉:@笨驴爱吃胡萝卜 没办法,方法一层层调用,这种讲述顺序已经是斟酌很久尽可能清楚了。。以后会多加图的:smile::smile:
      • _Echo_hu:跟着楼主的分析基本啃完了AF收获很大,了解了自己以前很多写代码的不足之处,真的非常感谢!期待楼主接下来的文章!
        涂耀辉:@dc55e7e5bc2b 能帮到你很开心 :smile:
      • bigParis:楼主这几篇AFN的源码分析, 信息量真的非常大, 辛苦, 感谢分享, 希望能但开一篇来分析AFN常见BUG(作者还没处理的bug).
        一代骄马:作者啥时候接着写af的bug啊~~期待
        MMPLetGo:@涂耀辉 写伤了。。。。哈哈哈哈哈哈
        涂耀辉:@bigParis 额。等过段时间吧。最近af写的有点写伤了。。:joy::joy:先完结了再说。。

      本文标题:AFNetworking之UIKit扩展与缓存实现

      本文链接:https://www.haomeiwen.com/subject/wtkpmttx.html