AFNetworking到底做了什么?(终)

112702646-426b7ad3b6d4e3ba
写在开头:
开始正文

首先我们来看看AF2.x的项目目录:

122702646-8c760bc6c51943f0

AF2.X源码结构图.png

除了UIKit扩展外,大概就是上述这么多类,其中最重要的有3个类:

1)AFURLConnectionOperation
2)AFHTTPRequestOperation
3)AFHTTPRequestOperationManager

  • 大家都知道,AF2.x是基于NSURLConnection来封装的,而NSURLConnection的创建以及数据请求,就被封装在AFURLConnectionOperation这个类中。所以这个类基本上是AF2.x最底层也是最核心的类。
  • AFHTTPRequestOperation是继承自AFURLConnectionOperation,对它父类一些方法做了些封装。
  • AFHTTPRequestOperationManager则是一个管家,去管理这些这些operation
我们接下来按照网络请求的流程去看看AF2.x的实现:

注:本文会涉及一些NSOperationQueueNSOperation方面的知识,如果对这方面的内容不了解的话,可以先看看雷纯峰的这篇:
iOS 并发编程之 Operation Queues

首先,我们来写一个get或者post请求:

就这么简单的几行代码,完成了一个网络请求。

接着我们来看看AFHTTPRequestOperationManager的初始化方法:

初始化方法很简单,基本和AF3.x类似,除了一下两点:
1)设置了一个operationQueue,这个队列,用来调度里面所有的operation,在AF2.x中,每一个operation就是一个网络请求。
2)设置shouldUseCredentialStorage为YES,这个后面会传给operationoperation会根据这个值,去返回给代理,系统是否做https的证书验证。

然后我们来看看get方法:

方法很简单,如下:
1)用self.requestSerializer生成了一个request,至于如何生成,可以参考之前的文章,这里就不赘述了。
2)生成了一个AFHTTPRequestOperation,然后把这个operation加到我们一开始创建的queue中。

其中创建AFHTTPRequestOperation方法如下:

方法创建了一个AFHTTPRequestOperation,并把自己的一些参数交给了这个operation处理。

接着往里看:

除了设置了一个self.responseSerializer,实际上是调用了父类,也是我们最核心的类AFURLConnectionOperation的初始化方法,首先我们要明确这个类是继承自NSOperation的,然后我们接着往下看:

初始化方法中,初始化了一些属性,下面我们来简单的介绍一下这些属性:

  1. _state设置为AFOperationReadyState 准备就绪状态,这是个枚举:

    这个_state标志着这个网络请求的状态,一共如上4种状态。这些状态其实对应着operation如下的状态:

    并且还复写了这些属性的get方法,用来和自定义的state一一对应:
  2. self.lock这个锁是用来提供给本类一些数据操作的线程安全,至于为什么要用递归锁,是因为有些方法可能会存在递归调用的情况,例如有些需要锁的方法可能会在一个大的操作环中,形成递归。而AF使用了递归锁,避免了这种情况下死锁的发生
  3. 初始化了self.runLoopModes,默认为NSRunLoopCommonModes
  4. 生成了一个默认的 self.securityPolicy,关于这个policy执行的https认证,可以见楼主之前的文章。

这个类为了自定义operation的各种状态,而且更好的掌控它的生命周期,复写了operationstart方法,当这个operation在一个新线程被调度执行的时候,首先就调入这个start方法中,接下来我们它的实现看看:

这个方法判断了当前的状态,是取消还是准备就绪,然后去调用了各自对应的方法。

  • 注意这些方法都是在另外一个线程中去调用的,我们来看看这个线程:

    这两个方法基本上是被许多人举例用过无数次了…
  • 这是一个单例,用NSThread创建了一个线程,并且为这个线程添加了一个runloop,并且加了一个NSMachPort,来防止runloop直接退出。
  • 这条线程就是AF用来发起网络请求,并且接受网络请求回调的线程,仅仅就这一条线程(到最后我们来讲为什么要这么做)。和我们之前讲的AF3.x发起请求,并且接受请求回调时的处理方式,遥相呼应。

我们接着来看如果准备就绪,start调用的方法:

接着在常驻线程中,并且不阻塞的方式,在我们self.runLoopModes的模式下调用:

这个方法做了以下几件事:

  1. 首先这个方法创建了一个NSURLConnection,设置代理为自己,startImmediately为NO,至于这个参数干什么用的,我们来看看官方文档:

    startImmediately
    YES if the connection should begin loading data immediately, otherwise NO. If you pass NO, the connection is not scheduled with a run loop. You can then schedule the connection in the run loop and mode of your choice by calling scheduleInRunLoop:forMode: .

    大意是,这个值默认为YES,而且任务完成的结果会在主线程的runloop中回调。如果我们设置为NO,则需要调用我们下面看到的:

    去注册一个runloop和mode,它会在我们指定的这个runloop所在的线程中回调结果。

  2. 值得一提的是这里调用了:

    这个outputStream在get方法中被初始化了:

    这里数据请求和拼接并没有用NSMutableData,而是用了outputStream,而且把写入的数据,放到内存中。

    • 其实讲道理来说outputStream的优势在于下载大文件的时候,可以以流的形式,将文件直接保存到本地,这样可以为我们节省很多的内存,调用如下方法设置:
    • 但是这里是把流写入内存中,这样其实这个节省内存的意义已经不存在了。那为什么还要用呢?这里我猜测的是就是为了用它这个可以注册在某一个runloop的指定mode下。 虽然AF使用这个outputStream是肯定在这个常驻线程中的,不会有线程安全的问题。但是要注意它是被声明在.h中的:

      难保外部不会在其他线程对这个数据做什么操作,所以它相对于NSMutableData作用就体现出来了,就算我们在外部其它线程中去操作它,也不会有线程安全的问题。
  3. 这个connection开始执行了。
  4. 到主线程发送一个任务开始执行的通知。
132702646-7b6d90f1e5106673

分割图.png
接下来网络请求开始执行了,就开始触发connection的代理方法了:
142702646-189b9c6e86564129

代理方法.png

AF2.x一共实现了如上这么多代理方法,这些代理方法,作用大部分和我们之前讲的NSURLSession的代理方法类似,我们只挑几个去讲,如果需要了解其他的方法作用,可以参考楼主之前的文章。

重点讲下面这四个代理:

注意,有一点需要说明,我们之前是把connection注册在我们常驻线程的runloop中了,所以以下所有的代理方法,都是在这仅有的一条常驻线程中回调。

第一个代理

没什么好说的,就是收到响应后,把response赋给自己的属性。

第二个代理

这个方法看起来长,其实容易理解而且简单,它只做了3件事:

  1. outputStream拼接数据,具体如果拼接,大家可以读注释自行理解下。
  2. 如果出错则调用:connection:didFailWithError:也就是网络请求失败的代理,我们一会下面就会讲。
  3. 在主线程中回调下载进度。
第三个代理

  • 这个代理是任务完成之后调用。我们从outputStream拿到了最后下载数据,然后关闭置空了outputStream。并且清空了connection。调用了finish:

    把当前任务状态改为已完成,并且到主线程发送任务完成的通知。,这里我们设置状态为已完成。其实调用了我们本类复写的set的方法(前面遗漏了,在这里补充):

    这个方法改变state的时候,并且发送了KVO。大家了解NSOperationQueue就知道,如果对应的operation的属性finnished被设置为YES,则代表当前operation结束了,会把operation从队列中移除,并且调用operationcompletionBlock这点很重要,因为我们请求到的数据就是从这个completionBlock中传递回去的(下面接着讲这个完成Block,就能从这里对接上了)。
第四个代理

唯一需要说一下的就是这里给self.error赋值,之后完成Block会根据这个error,去判断这次请求是成功还是失败。

至此我们把AFURLConnectionOperation的业务主线讲完了。

132702646-7b6d90f1e5106673

分割图.png

我们此时数据请求完了,数据在self.responseData中,接下来我们来看它是怎么回到我们手里的。
我们回到AFURLConnectionOperation子类AFHTTPRequestOperation,有这么一个方法:

一开始我们在AFHTTPRequestOperationManager中是调用过这个方法的:

  • 我们在把成功和失败的Block传给了这个方法。
  • 这个方法也很好理解,就是设置我们之前提到过得completionBlock当自己数据请求完成,就会调用这个Block。然后我们在这个Block中调用传过来的成功或者失败的Block。如果error为空,说明请求成功,把数据传出去,否则为失败,把error信息传出。
  • 这里也类似AF3.x,可以自定义一个完成组和完成队列。数据可以在我们自定义的完成组和队列中回调出去。
  • 除此之外,还有一个有意思的地方:

    之前我们说过,这是在忽略编译器的一些警告。

    • -Wgnu就不说了,是忽略?:。
    • 值得提下的是-Warc-retain-cycles,这里忽略了循环引用的警告。我们仔细看看就知道self持有了completionBlock,而completionBlock内部持有self。这里确实循环引用了。那么AF是如何解决这个循环引用的呢?

我们在回到AFURLConnectionOperation,还有一个方法我们之前没讲到,它复写了setCompletionBlock这个方法。

注意,它在我们设置的block调用结束的时候,主动的调用:

把Block置空,这样循环引用不复存在了。

好像我们还遗漏了一个东西,就是返回的数据做类型的解析。其实还真不是楼主故意这样东一块西一块的,AF2.x有些代码确实是这样零散。。当然仅仅是相对3.x来说。AFNetworking整体代码质量,以及架构思想已经强过绝大多数开源项目太多了。。这一点毋庸置疑。

我们来接着看看数据解析在什么地方被调用的把:

AFHTTPRequestOperation 复写了 responseObject 的get方法,
并且把数据按照我们需要的类型(json、xml等等)进行解析。至于如何解析,可以参考楼主之前AF系列的文章,这里就不赘述了。

有些小伙伴可能会说,楼主你是不是把AFSecurityPolicy给忘了啊,其实并没有,它被在 AFURLConnectionOperation中https认证的代理中被调用,我们之前系列的文章已经讲的非常详细了,感兴趣的朋友可以翻到前面的文章去看看。

至此,AF2.x整个业务流程就结束了。

接下来,我们来总结总结AF2.x整个业务请求的流程:

152702646-eb8527a94ca620f6

AF2.x请求流程图.png

PS.图片是用page画的,第一次用,画了半个小时有没有…有没有感受到楼主很走心…最近发现写文图太少了,以后会多配图的。来加深大家的理解…

如上图,我们来梳理一下整个流程:
  • 最上层的是AFHTTPRequestOperationManager,我们调用它进行get、post等等各种类型的网络请求
  • 然后它去调用AFURLRequestSerialization做request参数拼装。然后生成了一个AFHTTPRequestOperation实例,并把request交给它。然后把AFHTTPRequestOperation添加到一个NSOperationQueue中。
  • 接着AFHTTPRequestOperation拿到request后,会去调用它的父类AFURLConnectionOperation的初始化方法,并且把相关参数交给它,除此之外,当父类完成数据请求后,它调用了AFURLResponseSerialization把数据解析成我们需要的格式(json、XML等等)。
  • 最后就是我们AF最底层的类AFURLConnectionOperation,它去数据请求,并且如果是https请求,会在请求的相关代理中,调用AFSecurityPolicy做https认证。最后请求到的数据返回。

这就是AF2.x整个做网络请求的业务流程。

我们来解决解决之前遗留下来的问题:为什么AF2.x需要一条常驻线程?

首先如果我们用NSURLConnection,我们为了获取请求结果有以下三种选择:

  1. 在主线程调异步接口
  2. 每一个请求用一个线程,对应一个runloop,然后等待结果回调。
  3. 只用一条线程,一个runloop,所有结果回调在这个线程上。

很显然AF选择的是第3种方式,创建了一条常驻线程专门处理所有请求的回调事件,这个模型跟nodejs有点类似,我们来讨论讨论不选择另外两种方式的原因:

  1. 试想如果我们所有的请求都在主线程中异步调用,好像没什么不可以?那为什么AF不这么做呢…在这里有两点原因(楼主个人总结的,有不同意见,欢迎讨论):
    • 第一是,如果我们放到主线程去做,势必要这么写:

      这样NSURLConnection的回调会被放在主线程中NSDefaultRunLoopMode中,这样我们在其它类似UITrackingRunLoopMode模式下,我们是得不到网络请求的结果的,这显然不是我们想要的,那么我们势必需要调用:

      把它加入`NSRunLoopCommonModes中,试想如果有大量的网络请求,同时回调回来,就会影响我们的UI体验了。
    • 另外一点原因是,如果我们请求数据返回,势必要进行数据解析,解析成我们需要的格式,那么这些解析都在主线程中做,给主线程增加额外的负担。
      又或者我们回调回来开辟一个新的线程去做数据解析,那么我们有n个请求回来开辟n条线程带来的性能损耗,以及线程间切换带来的损耗,是不是一笔更大的开销。

    所以综述两点原因,我们并不适合在主线程中回调。

  2. 我们一开始就开辟n条线程去做请求,然后设置runloop保活住线程,等待结果回调。
    • 其实看到这,大家想想都觉得这个方法很傻,为了等待不确定的请求结果,阻塞住线程,白白浪费n条线程的开销。

综上所述,这就是AF2.x需要一条常驻线程的原因了

至此我们把AF2.x核心流程分析完了。
132702646-7b6d90f1e5106673
分割图.png

接着到我们本系列一个最终总结了: AFNetworking到底做了什么?

  • 相信如果从头看到尾的小伙伴,心里都有了一个属于自己的答案。其实在楼主心里,实在不想去总结它,因为AFNetworking中凝聚了太多大牛的思想,根本不是你看完几遍源码所能去议论的。但是想想也知道,如果我说不总结,估计有些看到这的朋友杀人的心都有…
  • 所以我还是赶鸭子上架,来总结总结它。
AFNetworking的作用总结:

一. 首先我们需要明确一点的是:
相对于AFNetworking2.x,AFNetworking3.x确实没那么有用了。AFNetworking之前的核心作用就是为了帮我们去调度所有的请求。但是最核心地方却被苹果的NSURLSession给借鉴过去了,嗯…是借鉴。这些请求的调度,现在完全由NSURLSession给做了,AFNetworking3.x的作用被大大的削弱了。
二. 但是除此之外,其实它还是很有用的:

  1. 首先它帮我们做了各种请求方式request的拼接。想想如果我们用NSURLSession,我们去做请求,是不是还得自己去考虑各种请求方式下,拼接参数的问题。
  2. 它还帮我们做了一些公用参数(session级别的),和一些私用参数(task级别的)的分离。它用Block的形式,支持我们自定义一些代理方法,如果没有实现的话,AF还帮我们做了一些默认的处理。而如果我们用NSURLSession的话,还得参照AF这么一套代理转发的架构模式去封装。
  3. 它帮我们做了自定义的https认证处理。看过楼主之前那篇AFNetworking之于https认证的朋友就知道,如果我们自己用NSURLSession实现那几种自定义认证,需要多写多少代码…
  4. 对于请求到的数据,AF帮我们做了各种格式的数据解析,并且支持我们设置自定义的code范围,自定义的数据方式。如果不在这些范围中,则直接调用失败block。如果用NSURLSession呢?这些都自己去写吧…(你要是做过各种除json外其他的数据解析,就会知道这里面坑有多少…)
  5. 对于成功和失败的回调处理。AF帮我们在数据请求到,到回调给用户之间,做了各种错误的判断,保证了成功和失败的回调,界限清晰。在这过程中,AF帮我们做了太多的容错处理,而NSURLSession呢?只给了一个完成的回调,我们得多做多少判断,才能拿到一个确定能正常显示的数据?
  6. ……

光是这些网络请求的业务逻辑,AF帮我们做的就太多太多,当然还远不仅于此。它用凝聚着许多大牛的经验方式,帮我在有些处理中做了最优的选择,比如我们之前说到的,回调线程数设置为1的问题…帮我们绕开了很多的坑,比如系统内部并行创建task导致id不唯一等等…

三. 而如果我们需要一些UIKit的扩展,AF则提供了最稳定,而且最优化实现方式:

  • 就比如之前说到过得那个状态栏小菊花,如果是我们自己去做,得多写多少代码,而且实现的还没有AF那样质量高。
  • 又或者AFImageDownloader,它对于组图片之间的下载协调,以及缓存使用的之间线程调度。对于线程,锁,以及性能各方面权衡,找出最优化的处理方式,试问小伙伴们自己基于NSURLSession去写,能到做几分…

所以最后的结论是:AFNetworking虽然变弱了,但是它还是很有用的。用它真的不仅仅是习惯,而是因为它确实帮我们做了太多。

写在最后:
  • 这个系列终于结束了,从想要开始写这个系列,到真正结束,花了大半个月的时间。其实3.x源码早在刚出来的时候就读过了,为了写它,又拿出来重新读了一遍。而且为了让大家更容易理解,楼主在大部分代码,几乎是一行一个注释的在标注,在这里浪费了大量的时间。
    然而看到大家的赞和评论,还有有些素昧平生的朋友的打赏。让我发自内心的开心。这一切都太值得…
    如果你能看到这里,除了感谢还是感谢~以后还会分享更多好的文章给大家,谢谢~

最后献上一首歌:感恩的心~感谢有你~~happy ending…

162702646-efa4d2ceb4867322
1 收藏 评论

相关文章

可能感兴趣的话题



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