AFNetworking 之 UIKit 扩展与缓存实现

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

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

122702646-91661ed549cf34d2

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

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

132702646-0caaf80ee0a9725d

小菊花.png

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

接下来我们来讲讲这个类的实现:
  • 这个类的实现也非常简单,还记得我们之前讲的AF对NSURLSessionTask中做了一个Method Swizzling吗?大意是把它的resumesuspend方法做了一个替换,在原有实现的基础上添加了一个通知的发送。
  • 这个类就是基于这两个通知和task完成的通知来实现的。
首先我们来看看它的初始化方法:

  • 初始化如上,设置了一个state,这个state是一个枚举:

    这个state一共如上4种状态,其中两种应该很好理解,而延迟开始和延迟结束怎么理解呢?

    • 原来这是AF对请求菊花显示做的一个优化处理,试问如果一个请求时间很短,那么菊花很可能闪一下就结束了。如果很多请求过来,那么菊花会不停的闪啊闪,这显然并不是我们想要的效果。
    • 所以多了这两个参数:
      1)在一个请求开始的时候,我延迟一会在去转菊花,如果在这延迟时间内,请求结束了,那么我就不需要去转菊花了。
      2)但是一旦转菊花开始,哪怕很短请求就结束了,我们还是会去转一个时间再去结束,这时间就是延迟结束的时间。
  • 紧接着我们监听了三个通知,用来监听当前正在进行的网络请求的状态。
  • 然后设置了我们前面提到的这个转菊花延迟开始和延迟结束的时间,这两个默认值如下:

接着我们来看看三个通知触发调用的方法:

方法很简单,就是开始的时候增加了请求活跃数,结束则减少。调用了如下两个方法进行加减:

方法做了什么应该很容易看明白,这里需要注意的是,task的几个状态的通知,是会在多线程的环境下发送过来的。所以这里对活跃数的加减,都用了@synchronized这种方式的锁,进行了线程保护。然后回到主线程调用了updateCurrentStateForNetworkActivityChange

我们接着来看看这个方法:

  • 这个方法先是判断了我们一开始设置是否需要菊花的self.enabled,如果需要,才执行。
  • 这里主要是根据当前的状态,来判断下一个状态应该是什么。其中有这么一个属性self.isNetworkActivityOccurring:

    那么这个方法应该不难理解了。

这个类复写了currentState的set方法,每当我们改变这个state,就会触发set方法,而怎么该转菊花也在该方法中:

这个set方法就是这个类最核心的方法了。它的作用如下:

  • 这里根据当前状态,是否需要开始执行一个延迟开始或者延迟完成,又或者是否需要取消这两个延迟。
  • 还判断了,是否需要去转状态栏的菊花,调用了setNetworkActivityIndicatorVisible:方法:

    • 这个方法就是用来控制菊花是否转。并且支持一个自定义的Block,我们可以自己去拿到这个菊花是否应该转的状态值,去做一些自定义的处理。
    • 如果我们没有实现这个Block,则调用:

      去转菊花。

回到state的set方法中,我们除了控制菊花去转,还调用了以下4个方法:

这4个方法分别是开始延迟执行一个方法,和结束的时候延迟执行一个方法,和对应这两个方法的取消。其作用,注释应该很容易理解。
我们继续往下看,这两个延迟调用的到底是什么:

一个开始,一个完成调用,都设置了不同的currentState的值,又回到之前stateset方法中了。

至此这个AFNetworkActivityIndicatorManager类就讲完了,代码还是相当简单明了的。

142702646-426da66cf4d16567
2.UIImageView+AFNetworking

接下来我们来讲一个我们经常用的方法,这个方法的实现类是:UIImageView+AFNetworking.h
这是个类目,并且给UIImageView扩展了4个方法:

  • 前两个想必不用我说了,没有谁没用过吧…就是给一个UIImageView去异步的请求一张图片,并且可以设置一张占位图。
  • 第3个方法设置一张图,并且可以拿到成功和失败的回调。
  • 第4个方法,可以取消当前的图片设置请求。

无论SDWebImage,还是YYKit,或者AF,都实现了这么个类目。
AF关于这个类目UIImageView+AFNetworking的实现,依赖于这么两个类:AFImageDownloaderAFAutoPurgingImageCache
当然AFImageDownloader中,关于图片数据请求的部分,还是使用AFURLSessionManager来实现的。

接下来我们就来看看AFImageDownloader:

先看看初始化方法:

该类为单例,上述方法中,创建了一个sessionManager,这个sessionManager将用于我们之后的网络请求。从这里我们可以看到,这个类的网络请求都是基于之前AF自己封装的AFHTTPSessionManager

  • 在这里初始化了一系列的对象,需要讲一下的是AFImageDownloadPrioritizationFIFO,这个一个枚举值:

    这个枚举值代表着,一堆图片下载,执行任务的顺序。
  • 还有一个AFAutoPurgingImageCache的创建,这个类是AF做图片缓存用的。这里我们暂时就这么理解它,讲完当前类,我们再来补充它。
  • 除此之外,我们还看到一个cache:


    大家看到这可能迷惑了,怎么这么多cache,那AF做图片缓存到底用哪个呢?答案是AF自己控制的图片缓存用AFAutoPurgingImageCache,而NSUrlRequest的缓存由它自己内部根据策略去控制,用的是NSURLCache,不归AF处理,只需在configuration中设置上即可。

    • 那么看到这有些小伙伴又要问了,为什么不直接用NSURLCache,还要自定义一个AFAutoPurgingImageCache呢?原来是因为NSURLCache的诸多限制,例如只支持get请求等等。而且因为是系统维护的,我们自己的可控度不强,并且如果需要做一些自定义的缓存处理,无法实现。
    • 更多关于NSURLCache的内容,大家可以自行查阅。

接着上面的方法调用到这个最终的初始化方法中:

这边初始化了一些属性,这些属性跟着注释看应该很容易明白其作用。主要需要注意的就是,这里创建了两个queue:一个串行的请求queue,和一个并行的响应queue。

  • 这个串行queue,是用来做内部生成task等等一系列业务逻辑的。它保证了我们在这些逻辑处理中的线程安全问题(迷惑的接着往下看)。
  • 这个并行queue,被用来做网络请求完成的数据回调。

接下来我们来看看它的创建请求task的方法:

就这么一个非常非常长的方法,这个方法执行的内容都是在我们之前创建的串行queue中,同步的执行的,这是因为这个方法绝大多数的操作都是需要线程安全的。可以对着源码和注释来看,我们在这讲下它做了什么:

  1. 首先做了一个url的判断,如果为空则返回失败Block。
  2. 判断这个需要请求的url,是不是已经被生成的task中,如果是的话,则多添加一个回调处理就可以。回调处理对象为AFImageDownloaderResponseHandler。这个类非常简单,总共就如下3个属性:

    当这个task完成的时候,会调用我们添加的回调。
  3. 关于AFImageDownloaderMergedTask,我们在这里都用的是这种类型的task,其实这个task也很简单:

    其实就是除了NSURLSessionDataTask,多加了几个参数,URLIdentifieridentifier都是用来标识这个task的,responseHandlers是用来存储task完成后的回调的,里面可以存一组,当任务完成时候,里面的回调都会被调用。
  4. 接着去根据缓存策略,去加载缓存,如果有缓存,从self.imageCache中返回缓存,否则继续往下走。
  5. 走到这说明没相同url的task,也没有cache,那么就开始一个新的task,调用的是AFUrlSessionManager里的请求方法生成了一个task(这里我们就不赘述了,可以看之前的楼主之前的文章)。然后做了请求完成的处理。注意,这里处理实在我们一开始初始化的并行queue:self.responseQueue中的,这里的响应处理是多线程并发进行的。
    1)完成,则调用如下方法把这个task从全局字典中移除:

    2)去循环这个task的responseHandlers,调用它的成功或者失败的回调。
    3)并且调用下面两个方法,去减少正在请求的任务数,和开启下一个任务:

    这里需要注意的是,跟我们本类的一些数据相关的操作,都是在我们一开始的串行queue中同步进行的。
    4)除此之外,如果成功,还把成功请求到的数据,加到AF自定义的cache中:
  6. NSUUID生成的唯一标识,去生成AFImageDownloaderResponseHandler,然后生成一个AFImageDownloaderMergedTask,把之前第5步生成的createdTask和回调都绑定给这个AF自定义可合并回调的task,然后这个task加到全局的task映射字典中,key为url:
  7. 判断当前正在下载的任务是否超过最大并行数,如果没有则开始下载,否则先加到等待的数组中去:


    • 先判断并行数限制,如果小于最大限制,则开始下载,把当前活跃的request数量+1。
    • 如果暂时不能下载,被加到等待下载的数组中去的话,会根据我们一开始设置的下载策略,是先进先出,还是后进先出,去插入这个下载任务。
  8. 最后判断这个mergeTask是否为空。不为空,我们生成了一个AFImageDownloadReceipt,绑定了一个UUID。否则为空返回nil:

    这个AFImageDownloadReceipt仅仅是多封装了一个UUID:

    这么封装是为了标识每一个task,我们后面可以根据这个AFImageDownloadReceipt来对task做取消操作。

这个AFImageDownloader中最核心的方法基本就讲完了,还剩下一些方法没讲,像前面讲到的task的取消的方法:

方法比较简单,大家自己看看就好。至此`AFImageDownloader这个类讲完了。如果大家看的感觉比较绕,没关系,等到最后我们一起来总结一下,捋一捋。

142702646-426da66cf4d16567

我们之前讲到AFAutoPurgingImageCache这个类略过去了,现在我们就来补充一下这个类的相关内容:
首先来讲讲这个类的作用,它是AF自定义用来做图片缓存的。我们来看看它的初始化方法:

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

  1. 声明了一个默认的内存缓存大小100M,还有一个意思是如果超出100M之后,我们去清除缓存,此时仍要保留的缓存大小60M。(如果还是不理解,可以看后文,源码中会讲到)
  2. 创建了一个并行queue,这个并行queue,这个类除了初始化以外,所有的方法都是在这个并行queue中调用的。
  3. 创建了一个cache字典,我们所有的缓存数据,都被保存在这个字典中,key为url,value为AFCachedImage
    关于这个AFCachedImage,其实就是Image之外封装了几个关于这个缓存的参数,如下:
  4. 添加了一个通知,监听内存警告,当发成内存警告,调用该方法,移除所有的缓存,并且把当前缓存数置为0:

    注意这个类大量的使用了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

接着我们来看看这个类最核心的一个方法:

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

  1. 设置缓存到字典里,并且把对应的缓存大小设置到当前已缓存的数量属性中。
  2. 判断是缓存超出了我们设置的最大缓存100M,如果是的话,则清除掉部分早时间的缓存,清除到缓存小于我们溢出后保留的内存60M以内。

当然在这里更需要说一说的是dispatch_barrier_async,这里整个类都没有使用dispatch_async,所以不存在是为了做一个栅栏,来同步上下文的线程。其实它在本类中的作用很简单,就是一个串行执行。

  • 讲到这,小伙伴们又疑惑了,既然就是只是为了串行,那为什么我们不用一个串行queue就得了?非得用dispatch_barrier_async干嘛?其实小伙伴要是看的仔细,就明白了,上文我们说过,我们要用dispatch_barrier_sync来保证线程安全。如果我们使用串行queue,那么线程是极其容易死锁的。

还有剩下的几个方法:

这几个方法都很简单,大家自己看看就好了,就不赘述了。至此AFAutoPurgingImageCache也讲完了,我们还是等到最后再来总结。

142702646-426da66cf4d16567

我们绕了一大圈,总算回到了UIImageView+AFNetworking这个类,现在图片下载的方法,和缓存的方法都有了,实现这个类也是水到渠成的事了。

我们来看下面我们绝大多数人很熟悉的方法,看看它的实现:

上述方法按顺序往下调用,第二个方法给head的Accept类型设置为Image。接着调用到第三个方法,也是这个类目唯一一个重要的方法:

这个方法,细节的地方可以关注注释,这里总结一下做了什么:
1)去判断url是否为空,如果为空则取消task,调用如下方法:

  • 这里注意cancelImageDownloadTask中,调用了self.af_activeImageDownloadReceipt这么一个属性,看看定义的地方:

    我们现在是给UIImageView添加的一个类目,所以我们无法直接添加属性,而是使用的是runtime的方式来生成set和get方法生成了一个AFImageDownloadReceipt类型的属性。看过上文应该知道这个对象里面就一个task和一个UUID。这个属性就是我们这次下载任务相关联的信息。

2)然后做了一系列判断,见注释。
3)然后生成了一个我们之前分析过得AFImageDownloader,然后去获取缓存,如果有缓存,则直接读缓存。还记得AFImageDownloader里也有一个读缓存的方法么?那个是和cachePolicy相关的,而这个是有缓存的话直接读取。不明白的可以回过头去看看。
4)走到这说明没缓存了,然后就去用AFImageDownloader,我们之前讲过的方法,去请求图片。完成后,则调用成功或者失败的回调,并且置空属性self.af_activeImageDownloadReceipt,成功则设置图片。

除此之外还有一个取消这次任务的方法:

其实也是去调用我们之前讲过的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到底做了什么?(终)

1 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部