美文网首页
IOS框架:AFNetworking(下)

IOS框架:AFNetworking(下)

作者: 时光啊混蛋_97boy | 来源:发表于2020-10-26 06:22 被阅读0次

    原创:知识点总结性文章
    创作不易,请珍惜,之后会持续更新,不断完善
    个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
    温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

    目录

    • 一、功能模块
    • 二、源码解析
      • 1、初始化方法
      • 2、AFHTTPSessionManager中GET请求方法的源码实现
        • a、request的拼接
        • b、生成dataTask
      • 3、AFURLSessionManager 中代理方法的实现
        • a、NSURLSessionDelegate的实现
        • b、NSURLSessionDelegate转发到AF自定义的deleagate
      • 4、AFURLResponseSerialization 如何解析数据
      • 5、AF整个流程和线程的关系
    • 三、AF2.x版本的核心实现
      • 1、Get请求
      • 2、AFHTTPRequestOperationManager的初始化方法
      • 3、Get方法的实现
      • 4、connection的代理方法
      • 5、通过setCompletionBlockWithSuccess方法接收responseData
      • 6、数据解析
      • 7、问题:为什么AF2.x需要一条常驻线程?
    • 四、AFNetworking的作用总结
    • 五、AFSecurityPolicy实现https认证需求
      • 1、NSURLSessionDelegate中的代理方法:didReceiveChallenge
      • 2、AFSecurityPolicy实现https认证
        • a、创建AFSecurityPolicy
        • b、evaluateServerTrust:方法的内部实现
      • 3、自签名的证书
    • 六、UIKit扩展与缓存实现
      • 1、AFNetworkActivityIndicatorManager :网络请求时状态栏的小菊花
        • a、使用方式
        • b、初始化
      • 2、UIImageView+AFNetworking :请求网络图片
        • a、图片下载类AFImageDownloader的初始化方法
        • b、图片下载类AFImageDownloader创建请求task的方法
        • c、图片缓存类AFAutoPurgingImageCache的初始化方法
        • d、图片缓存类AFAutoPurgingImageCache的核心方法
        • e、setImageWithURL 设置图片方法
        • f、总结请求图片、缓存、设置图片的流程
    • Demo
    • 参考文献

    IOS框架:AFNetworking(中)

    六、UIKit扩展与缓存实现

    1、AFNetworkActivityIndicatorManager :网络请求时状态栏的小菊花

    前言:AF对NSURLSessionTask中做了一个Method Swizzling,把它的resumesuspend方法做了一个替换,在原有实现的基础上添加了一个通知的发送。AFNetworkActivityIndicatorManager就是基于这两个通知和task完成的通知来实现的。

    a、使用方式
    #import <AFNetworkReachabilityManager.h>
    
    [[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
    
    b、初始化
    + (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是一个枚举:

    typedef NS_ENUM(NSInteger, AFNetworkActivityManagerState) {
        AFNetworkActivityManagerStateNotActive,//没有请求
        AFNetworkActivityManagerStateDelayingStart,//请求延迟开始
        AFNetworkActivityManagerStateActive,//请求进行中
        AFNetworkActivityManagerStateDelayingEnd//请求延迟结束
    };
    

    延迟开始和延迟结束怎么理解呢?原来这是AF对请求菊花显示做的一个优化处理,试问如果一个请求时间很短,那么菊花很可能闪一下就结束了。如果很多请求过来,那么菊花会不停的闪啊闪,这显然并不是我们想要的效果。所以多了这两个参数,在一个请求开始的时候,延迟一会再去转动菊花,如果在这延迟时间内,请求结束了,那么就不需要去转菊花了。但是一旦转动菊花这个动画开始,哪怕很短请求就结束了,还是会去转一个时间片再结束,这时间就是延迟结束的时间。

    ❷ 接着监听了开始下载、挂起、完成的通知,即监听了当前正在进行的网络请求的状态。
    通知触发调用的方法:

    //请求开始
    - (void)networkRequestDidStart:(NSNotification *)notification {
        if ([AFNetworkRequestFromNotification(notification) URL]) {
            //增加请求活跃数
            [self incrementActivityCount];
        }
    }
    
    //请求结束
    - (void)networkRequestDidFinish:(NSNotification *)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

    接着探究updateCurrentStateForNetworkActivityChange:方法的实现:

    - (void)updateCurrentStateForNetworkActivityChange {
         //如果是允许小菊花
        if (self.enabled) {
            switch (self.currentState) {
                case AFNetworkActivityManagerStateNotActive://不活跃
                    if (self.isNetworkActivityOccurring) {//判断活跃数,大于0为YES
                        //设置状态为延迟开始
                        [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.isNetworkActivityOccurringGetter方法如下:

    //判断是否活跃
    - (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"];
        }
    }
    

    转动状态栏的菊花的setNetworkActivityIndicatorVisible方法的实现如下:

    //控制菊花转动
    - (void)setNetworkActivityIndicatorVisible:(BOOL)networkActivityIndicatorVisible {
        if (_networkActivityIndicatorVisible != networkActivityIndicatorVisible) {
            [self willChangeValueForKey:@"networkActivityIndicatorVisible"];
            @synchronized(self) {
                 _networkActivityIndicatorVisible = networkActivityIndicatorVisible;
            }
            [self didChangeValueForKey:@"networkActivityIndicatorVisible"];
            
            if (self.networkActivityActionBlock) {//支持自定义的Block,拿到这个菊花是否应该转的状态值,去自己控制小菊花
                self.networkActivityActionBlock(networkActivityIndicatorVisible);
            } else {// 如果我们没有实现这个Block,则调用系统的方法去转动菊花
                [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible];
            }
        }
    }
    

    回到stateset方法中,除了控制菊花去转动,还调用了以下4个方法:

    //开启一个定时器延迟去转菊花
    - (void)startActivationDelayTimer {
        //开始任务到结束的时间,默认为1秒,如果1秒就结束,那么不转菊花,即延迟开始转动,该方法只执行一次
        self.activationDelayTimer = [NSTimer
                                     timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO];
        //添加到主线程runloop去触发
        [[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes];
    }
    
    //开启延迟完成timer
    - (void)startCompletionDelayTimer {
        //先取消之前的
        [self.completionDelayTimer invalidate];
        
        //完成任务到下一个任务开始,默认为0.17秒,如果0.17秒内就开始下一个任务,那么继续转动不停止,即延迟结束菊花转动
        self.completionDelayTimer = [NSTimer timerWithTimeInterval:self.completionDelay target:self selector:@selector(completionDelayTimerFired) userInfo:nil repeats:NO];
        
        //添加到主线程runloop去触发
        [[NSRunLoop mainRunLoop] addTimer:self.completionDelayTimer forMode:NSRunLoopCommonModes];
    }
    
    //取消延迟开始的timer
    - (void)cancelActivationDelayTimer {
        [self.activationDelayTimer invalidate];
    }
    
    //取消延迟完成的timer
    - (void)cancelCompletionDelayTimer {
        [self.completionDelayTimer invalidate];
    }
    

    定时器开始转动和结束转动时调用的方法如下:

    //开始转动时调用
    - (void)activationDelayTimerFired {
        if (self.networkActivityOccurring) {//活跃状态,即活跃数大于1才转
            //设置了不同的currentState的值,又回到之前state的set方法中了
            [self setCurrentState:AFNetworkActivityManagerStateActive];
        } else {
            [self setCurrentState:AFNetworkActivityManagerStateNotActive];
        }
    }
    
    //转动结束时候调用
    - (void)completionDelayTimerFired {
        [self setCurrentState:AFNetworkActivityManagerStateNotActive];
    }
    

    ❸ 然后设置了前面提到的这个转菊花延迟开始和延迟结束的时间,这两个默认值如下:

    static NSTimeInterval const kDefaultAFNetworkActivityManagerActivationDelay = 1.0;
    static NSTimeInterval const kDefaultAFNetworkActivityManagerCompletionDelay = 0.17;
    

    2、UIImageView+AFNetworking :请求网络图片

    UIImageView扩展了4个方法:

    - (void)setImageWithURL:(NSURL *)url;
    //给一个UIImageView去异步的请求一张图片,并且可以设置一张占位图
    - (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+AFNetworking的实现依赖于这么两个类:AFImageDownloaderAFAutoPurgingImageCache

    a、图片下载类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];
        //创建了一个sessionManager,将用于基于AF自己封装的AFHTTPSessionManager的网络请求
        AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:defaultConfiguration];
        sessionManager.responseSerializer = [AFImageResponseSerializer serializer];
    
        return [self initWithSessionManager:sessionManager
                     downloadPrioritization:AFImageDownloadPrioritizationFIFO
                     maximumActiveDownloads:4
                                 imageCache:[[AFAutoPurgingImageCache alloc] init]];//AFAutoPurgingImageCache的创建,这个类是AF做图片缓存用的
    }
    
    + (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;
    }
    

    AFImageDownloadPrioritizationFIFO这个枚举值代表着,一堆图片下载,执行任务的顺序。

    typedef NS_ENUM(NSInteger, AFImageDownloadPrioritization) {
        AFImageDownloadPrioritizationFIFO,//先进先出
        AFImageDownloadPrioritizationLIFO//后进先出
    };
    

    AF自己控制的图片缓存用AFAutoPurgingImageCache,而NSUrlRequest的缓存由它自己内部根据策略去控制,用的是NSURLCache,不归AF处理,只需在configuration中设置上即可。

    //设置一个系统缓存,内存缓存为20M,磁盘缓存为150M,
    //这个是系统级别维护的缓存
    + (NSURLCache *)defaultURLCache {
        return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
                                             diskCapacity:150 * 1024 * 1024
                                                 diskPath:@"com.alamofire.imagedownloader"];
    }
    

    为什么不直接用NSURLCache,还要自定义一个AFAutoPurgingImageCache呢?原来是因为NSURLCache的诸多限制,例如只支持get请求等等。而且因为是系统维护的,我们自己的可控度不强,如果需要做一些自定义的缓存处理,无法实现。

    进入最终的初始化方法:initWithSessionManager

    - (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,用来做内部生成task等,保证了线程安全问题
            self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);
    
            //创建并行响应queue,用来做网络请求完成的数据回调
            name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]];
            self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
        }
    
        return self;
    }
    
    b、图片下载类AFImageDownloader创建请求task的方法
    - (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
                                                      withReceiptID:(nonnull NSUUID *)receiptID
                                                            success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse  * _Nullable response, UIImage *responseObject))success
                                                            failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure {
        
        __block NSURLSessionDataTask *task = nil;
        
        //同步串行去做下载的事,生成一个task,这些事情都是在当前线程中串行同步做的,所以不用担心线程安全问题
        dispatch_sync(self.synchronizationQueue, ^{
            //一:首先做了一个url的判断,如果为空则直接返回失败Block
            
            //url字符串
            NSString *URLIdentifier = request.URL.absoluteString;
            if (URLIdentifier == nil) {//没Url
                if (failure) {//返回错误信息
                    NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
                    dispatch_async(dispatch_get_main_queue(), ^{
                        failure(request, nil, error);
                    });
                }
                return;
            }
            
            //二:判断这个需要请求的url,是不是已经被生成的task中,如果是的话,则多添加一个回调处理就可以直接返回
    
            //从自己task字典中根据Url去取AFImageDownloaderMergedTask,里面有task id url等等信息
            AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
            if (existingMergedTask != nil) {//如果这个任务已经存在
                //回调处理,里面包含成功和失败Block和UUid,当task完成的时候,会调用我们添加的回调
                AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure];
                //添加handler
                [existingMergedTask addResponseHandler:handler];
                //给task赋值
                task = existingMergedTask.task;
                return;
            }
            
            // 三:接着根据缓存策略加载缓存,如果有缓存则从self.imageCache中直接返回缓存,否则继续往下走
    
            //根据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;
            }
            
            // 四:走到这说明没相同url的task(没有正在请求中的request),同时也没有cache,那么就开始一个新的task
            // 调用的是AFUrlSessionManager里的请求方法生成了一个task
            // 然后通过多线程并发self.responseQueue做了请求完成的处理
            // 响应处理完成,则调用safelyRemoveMergedTaskWithURLIdentifier把task从全局字典中移除
            // 接着循环这个task的responseHandlers,调用它的成功或者失败的回调,并且请求成功还往cache里添加了请求到的数据
            // 然后减少正在请求的任务数,并且开启下一个任务
    
            //走到这说明既,也没有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) {
                                               
                                               if (handler.failureBlock) {
                                                   //主线程,调用失败的Block
                                                   dispatch_async(dispatch_get_main_queue(), ^{
                                                       handler.failureBlock(request, (NSHTTPURLResponse*)response, error);
                                                   });
                                               }
                                           }
                                       } else {//成功
                                           //根据request,往cache里添加请求到的数据
                                           [strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
    
                                           //去遍历task所有响应的处理
                                           for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
                                               if (handler.successBlock) {
                                                   //主线程,调用失败的Block
                                                   dispatch_async(dispatch_get_main_queue(), ^{
                                                       handler.successBlock(request, (NSHTTPURLResponse*)response, responseObject);
                                                   });
                                               }
                                           }
                                           
                                       }
                                   }
                                   //减少活跃的任务数
                                   [strongSelf safelyDecrementActiveTaskCount];
                                   //如果可以,则开启下一个任务
                                   [strongSelf safelyStartNextTaskIfNecessary];
                               });
                           }];
    
            // 五:用NSUUID生成的唯一标识,去生成AFImageDownloaderResponseHandler,然后生成一个AFImageDownloaderMergedTask
            // 把上一步生成的createdTask和回调都绑定给这个AF自定义可合并回调的task
            // 然后这个task加到全局的task映射字典中,key为url
            
            //创建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;
    
    
            // 六:判断当前正在下载的任务是否超过最大并行数,如果没有则开始下载,否则先加到等待的数组中去
            
            if ([self isActiveRequestCountBelowMaximumLimit]) {//如果小于最大并行数
                //则开始任务下载resume,把当前活跃的request数量+1
                [self startMergedTask:mergedTask];
            } else {
                //如果暂时不能下载,被加到等待下载的数组中去的话
                //会根据我们一开始设置的下载策略,是先进先出,还是后进先出,去插入这个下载任务
                [self enqueueMergedTask:mergedTask];
            }
            
            //拿到最终生成的task
            task = mergedTask.task;
        });
        
        // 七:最后判断这个mergeTask是否为空。
        // 不为空生成了一个AFImageDownloadReceipt,绑定了一个UUID。为空则返回nil
        if (task) {
            //创建一个AFImageDownloadReceipt并返回,里面就多一个receiptID(UUID)
            return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
        } else {
            //为空则返回nil
            return nil;
        }
    }
    

    这是个像火车一样长的方法,下面补充的是其中调用的方法的具体实现。

    ❶ 回调处理对象为AFImageDownloaderResponseHandler

    @interface AFImageDownloaderResponseHandler : NSObject
    
    @property (nonatomic, strong) NSUUID *uuid;
    @property (nonatomic, copy) void (^successBlock)(NSURLRequest*, NSHTTPURLResponse*, UIImage*);
    @property (nonatomic, copy) void (^failureBlock)(NSURLRequest*, NSHTTPURLResponse*, NSError*);
    
    @end
    
    @implementation AFImageDownloaderResponseHandler
    
    //初始化回调对象
    - (instancetype)initWithUUID:(NSUUID *)uuid
                         success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success
                         failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure {
        if (self = [self init]) {
            self.uuid = uuid;
            self.successBlock = success;
            self.failureBlock = failure;
        }
        return self;
    }
    
    - (NSString *)description {
        return [NSString stringWithFormat: @"<AFImageDownloaderResponseHandler>UUID: %@", [self.uuid UUIDString]];
    }
    
    @end
    

    这个类非常简单,当task完成的时候,会调用我们添加的回调。

    AFImageDownloaderMergedTask类也很简单,在NSURLSessionDataTask类的基础上多加了几个属性:

    @interface AFImageDownloaderMergedTask : NSObject
    
    @property (nonatomic, strong) NSString *URLIdentifier;// 用来标识这个task的
    @property (nonatomic, strong) NSUUID *identifier;// 用来标识这个task的
    @property (nonatomic, strong) NSURLSessionDataTask *task;
    
    // 用来存储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
    

    safelyRemoveMergedTaskWithURLIdentifier:方法作用是安全的移除,并返回当前被移除的AF的task,其实现为:

    //移除task
    - (AFImageDownloaderMergedTask*)safelyRemoveMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
        __block AFImageDownloaderMergedTask *mergedTask = nil;
        //用同步串行的形式,防止移除中出现重复移除一系列问题
        dispatch_sync(self.synchronizationQueue, ^{
            mergedTask = [self removeMergedTaskWithURLIdentifier:URLIdentifier];
        });
        return mergedTask;
    }
    

    safelyDecrementActiveTaskCount:方法的作用是减少活跃的任务数

    //减少活跃的任务数
    - (void)safelyDecrementActiveTaskCount {
        //回到串行queue
        dispatch_sync(self.synchronizationQueue, ^{
            if (self.activeRequestCount > 0) {
                self.activeRequestCount -= 1;
            }
        });
    }
    

    safelyStartNextTaskIfNecessary:方法的作用是条件允许的话则开启下一个任务:

    //如果可以,则开启下一个任务
    - (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;
                    }
                }
            }
        });
    }
    

    isActiveRequestCountBelowMaximumLimit:方法用来判断并行数限制:

    //判断并行数限制
    - (BOOL)isActiveRequestCountBelowMaximumLimit {
        return self.activeRequestCount < self.maximumActiveDownloads;
    }
    

    startMergedTask:方法可以开始下载:

    //开始下载
    - (void)startMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
        [mergedTask.task resume];
        
        //任务活跃数+1
        ++self.activeRequestCount;
    }
    

    enqueueMergedTask:方法把任务先加到数组里:

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

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

    💯:这么封装是为了标识每一个task,后面可以根据这个AFImageDownloadReceipt来对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;
    }
    
    c、图片缓存类AFAutoPurgingImageCache的初始化方法

    这个类的作用是用来做图片缓存的,它的初始化方法如下:

    - (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的字典,所有的缓存数据都被保存在这个字典中,key为url,value为AFCachedImage
            self.cachedImages = [[NSMutableDictionary alloc] init];
    
            NSString *queueName = [NSString stringWithFormat:@"com.alamofire.autopurgingimagecache-%@", [[NSUUID UUID] UUIDString]];
            //并行的queue,这个类除了初始化以外,所有的方法都是在这个并行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;
    }
    

    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;
    }
    
    - (NSString *)description {
        NSString *descriptionString = [NSString stringWithFormat:@"Idenfitier: %@  lastAccessDate: %@ ", self.identifier, self.lastAccessDate];
        return descriptionString;
    
    }
    
    @end
    

    ❷ 添加了一个通知,监听内存警告,当发成内存警告,调用removeAllImages:方法,移除所有的缓存,并且把当前缓存数置为0:

    //移除所有图片
    - (BOOL)removeAllImages {
        __block BOOL removed = NO;
    
        //没有用锁,而是使用了dispatch_barrier_sync(synchronizationQueue是个并行queue)
        //不需要再去开辟新的线程,浪费性能,只需要在原有线程,提交到synchronizationQueue队列中,阻塞了当前线程后执行即可
        //不仅同步了synchronizationQueue队列,而且阻塞了当前线程,所以保证了里面执行代码的线程安全问题
        //这样省去大量的开辟线程与使用锁带来的性能消耗
        dispatch_barrier_sync(self.synchronizationQueue, ^{
            if (self.cachedImages.count > 0) {
                [self.cachedImages removeAllObjects];
                self.currentMemoryUsage = 0;
                removed = YES;
            }
        });
        return removed;
    }
    
    d、图片缓存类AFAutoPurgingImageCache的核心方法

    添加imagecache里:

    - (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
        
    //一:设置缓存到字典里,并且把对应的缓存大小设置到当前已缓存的数量属性中
        
        //用dispatch_barrier_async,来同步这个并行队列,在本类中的作用很简单,就是一个串行执行
        //之前用dispatch_barrier_sync来保证线程安全,这里如果直接使用串行queue,那么线程是极其容易死锁的
        dispatch_barrier_async(self.synchronizationQueue, ^{
            //生成cache对象
            AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];
    
            //去之前cache的字典里取
            AFCachedImage *previousCachedImage = self.cachedImages[identifier];
            
            //如果有被缓存过
            if (previousCachedImage != nil) {
                //当前已经使用的内存大小减去旧cache图片的大小
                self.currentMemoryUsage -= previousCachedImage.totalBytes;
            }
    
            //把新cache的image加上去
            self.cachedImages[identifier] = cacheImage;
            //加上新cache内存大小
            self.currentMemoryUsage += cacheImage.totalBytes;
        });
    
    //二:判断是缓存超出了我们设置的最大缓存100M,如果是的话,则清除掉部分早时间的缓存,清除到缓存小于我们溢出后保留的内存60M以内
        //做缓存溢出的清除,清除的是早期的缓存
        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;
            }
        });
    }
    

    用到的其他方法:

    //根据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;
    }
    
    e、setImageWithURL 设置图片方法

    cancelImageDownloadTask:方法的作用是取消task

    //取消task
    - (void)cancelImageDownloadTask {
        if (self.af_activeImageDownloadReceipt != nil) {
            //取消事件回调响应
            [[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt];
            //置空回调对象
            [self clearActiveDownloadInformation];
         }
    }
    

    cancelImageDownloadTask:方法的作用是置空回调对象:

    // 置空回调对象
    - (void)clearActiveDownloadInformation {
        self.af_activeImageDownloadReceipt = nil;
    }
    

    self.af_activeImageDownloadReceipt属性:

    @interface UIImageView (_AFNetworking)
    
    @property (readwrite, nonatomic, strong, setter = af_setActiveImageDownloadReceipt:) AFImageDownloadReceipt *af_activeImageDownloadReceipt;
    
    @end
    
    @implementation UIImageView (_AFNetworking)
    
    //绑定属性的get方法。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+AFNetworking是给UIImageView添加的一个类目,所以无法直接添加属性,而是使用的是runtime的方式来生成setget方法生成了一个AFImageDownloadReceipt类型的属性。

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

    Demo

    Demo在我的Github上,欢迎下载。
    SourceCodeAnalysisDemo

    参考文献

    AFNetworking之UIKit扩展与缓存实现

    相关文章

      网友评论

          本文标题:IOS框架:AFNetworking(下)

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