天天看点

AFNetworking到底长啥样(下)一、环境搭建二、一个POST请求的前世今生三、AFNetworking中的干货

在AFNetworking到底长啥样(上)中简单介绍了AFN涉及的主要类及其结构,接下来以一个简单的POST请求探寻其内部是如何实现的。

服务器配置

本例中直接使用iMac自带的Apache,并为其开启PHP支持。在服务器目录下编写index.php文件如下:

编写测试App

创建一个测试App,在主界面上增加一个按钮,在按钮的点击函数中发起网络请求,如下:

启动测试App,点击按钮。接下里让我们看看AFN是如何优雅的管理网络请求的。

AFHTTPSessionManager的初始化

<code>self.manager</code>是<code>AFHTTPSessionManager</code>的实例,它使用类方法<code>+ manager</code>初始化。这个类方法最终调用如下方法:

①中调用父类的方法初始化父类相关属性,这包括:

②中设置其requestSerializer为AFHTTPRequestSerializer实例,③重设其responseSerializer为AFJSONResponseSerializer实例。

AFHTTPRequestSerializer的初始化

requestSerializer被设置成了AFHTTPRequestSerializer的实例,初始化是通过类方法<code>+serializer</code>实现的。内容包括:

AFJSONResponseSerializer的初始化

responseSerializer被设置成为了AFJSONResponseSerializer的实例,初始化是通过类方法<code>+serializer</code>实现的。内容包括:

至此必要的初始化已完成。

进入到POST函数中:

即最终生成一个dataTask,然后resume启动,并将dataTask返回。

生成的dataTask是通过如下函数实现的,本例中传入的参数值也已标明:

其内部执行的步骤如下:

Step1:调用requestSerializer的如下方法创建mutableRequest:

①是调用AFURLRequestSerialization协议方法对mutableRequest进行进一步处理,包括:

设置默认header,如在requestSerializer初始化时设置默认header:User-Agent和Accept-Language

处理参数

若用户自定义了处理参数的block(self.queryStringSerialization),则使用该block;否则使用默认的处理方式,会被最终处理成如下格式:name=layne&amp;age=30。

根据HTTPMethod决定参数串放到哪里:GET/HEAD/DELETE直接拼接到url;其他(如POST)方法放到HTTPBody中(使用stringEncoding(NSUTF8StringEncoding)编码),并设置Content-Type为application/x-www-form-urlencoded。

至此,生成的mutableRequest结构如下:

Step2:合并传入的header

执行之后mutableRequest结构如下:

Step3:判断Step1中生成mutableRequest的过程是否出错,若出错,则调用failureBlock并返回。

Step4:根据mutableRequest生成dataTask。

说明:

①中不直接生成dataTask,是因为在iOS8以下的系统上,若是在并行队列上创建dataTask会导致completionHandler调用出错。因此为了解决这个问题,针对iOS8以下系统,AFN使用自己维护的一个串行队列来创建dataTask。具体问题描述如下:

Due to this bug: http://openradar.appspot.com/radar?id=5871104061079552 in NSURLSessionTask, creating tasks on a concurrent queue can cause incorrect completionHandlers to get called. When a duplicate taskIdentifier is returned by the task, the previous completionHandler gets cleared out and replaced with the new one. If the data for the first request comes back before the second request's data, the first response is then called against the second completionHandler. I'm not sure what AFNetworking should do here — it could enforce creating tasks on a serial queue or it could just advise people to do so. We could also add a test to assert that the taskIdentifier is not a duplicate?

②的作用是为dataTask生成对应的delegate,以调用回调方法。

a. 为dataTask生成AFURLSessionManagerTaskDelegate实例。delegate内部维护着:

一个mutableData用来保存收到的response data。

一个uploadProgress和downloadProgress用来标明上传/下载进度。它们的cancel、suspend和resume操作与dataTask进行了绑定,即通过对它们进行cancel、suspend和resume操作就可以操作dataTask的cancel、suspend和resume。此外,还采用KVO监听uploadProgress和downloadProgress的进度变化,从而调用用户自定义的block:uploadProgressBlock和downloadProgressBlock。

b. delegate弱引用当前的manager。

c.delegate保存完成回调。

③返回最终的dataTask,并启动。

AFN是基于NSURLSession的,涉及以下几个协议:

NSURLSessionDelegate

NSURLSessionTaskDelegate

NSURLSessionDataDelegate

NSURLSessionDownloadDelegate

回调调用顺序:

① URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:

由于使用的是POST方式,因此该回调是首先调用的。逻辑:找到task对应的delegate=&gt;调用delegate的URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:来更新delegate维护的uploadProgress。若用户自定义了taskDidSendBodyData block,则调用。

② URLSession:task:didFinishCollectingMetrics:

该方法调用时机不定,用来收集整个请求的信息。逻辑:找到task对应的delegate=&gt;调用delegate的URLSession:task:didFinishCollectingMetrics:来为delegate.sessionTaskMetrics赋值。若用户自定义了taskDidFinishCollectingMetrics block,则调用。

③ URLSession:dataTask:didReceiveData:

收到response data时执行。逻辑:找到task对应的delegate=&gt;调用delegate的URLSession:task:didCompleteWithError:来更新delegate内部维护的downloadProgress并使用mutableData保存response data。若用户自定义了dataTaskDidReceiveData block,则调用。

④ URLSession:task:didCompleteWithError:

这是最后执行的回调。逻辑:找到task对应的delegate=&gt;调用delegate的URLSession:task:didCompleteWithError:,并删除delegate和task的对应关系。若用户自定义了taskDidComplete block,则调用。

数据的处理主要在delegate的URLSession:task:didCompleteWithError:中,主要做了以下几件工作:

构造userInfo字典:

若出错,则直接调用completeHandler回调,之后将上面的userInfo以通知AFNetworkingTaskDidCompleteNotification的形式广播出去。

若未出错,则使用responseSerializer(AFJSONResponseSerializer实例)将data转换为NSDictionary。若转换过程未出错,则在userInfo中增加{AFNetworkingTaskDidCompleteSerializedResponseKey:responseObject};若出错,则增加{AFNetworkingTaskDidCompleteErrorKey:error}。之后调用completeHandler回调,并将userInfo以通知AFNetworkingTaskDidCompleteNotification的形式广播出去。

经过上面的处理,最终要么error要么responseObject会返回到POST的回调中去。整个网络请求就完成了。

上面我们跟着一个简单的POST请求了解了AFN的整个工作流程,但还有一些细节我觉得还是值得我们学习的。

如果response不是标准格式的JSON数据或者我们需要对原始data进行加密解密操作该如何做?

答:将responseSerializer设置为AFHTTPResponseSerializer的实例,这样response data 会以原始data的形式返回给上层。AFHTTPResponseSerializer仅针对code和contenttype进行校验,而默认的AFJSONResponseSerializer除了校验code和contenttype之外还对JSON格式进行校验,并直接解析成NSDictionary。当然,如果想对response data想要做最全面的自定义处理,最直接的方式当然是自定义AFHTTPResponseSerializer的子类,并重写协议方法

如果返回的JSON数据中包含NSNull数据该如何处理?

答:将responseSerializer(AFJSONResponseSerializer实例)的removesKeysWithNullValues属性设置为YES。这样一来在JSON转换为NSDictionary之后会将NSDictionary中的NSNull值去除。

如果我想自己更改请求参数的格式(即不用默认的name=layne&amp;age=30这种)该如何设置?

答:调用requestSerializer的-setQueryStringSerializationWithBlock:设置自定义的格式。在requestSerializer处理参数的时候会先去判断queryStringSerialization block是否为空,若不为空,则使用该block处理参数。若为空,则使用默认的格式(如name=layne&amp;age=30)生成参数串。

除了使用GET/POST方法的回调,还可以通过什么方式获得网络请求结果?

答:还可以使用notification。AFN中有一个名为AFNetworkingTaskDidCompleteNotification的通知,可以通过监听该通知获取网络请求结果。AFURLSessionManagerTaskDelegate的URLSession:task:didCompleteWithError:中在调用completeHandler之后会发送通知:

如何更改回调所在的线程?

答:指定manager的completeQueue。这样回调就会在其他线程中执行。

AFURLSessionManager如何获取NSURLSession维护的task?

答:使用getTasksWithCompletionHandler:API并使用信号量保证线程安全。

若我想取消一个刚发起的网络请求该如何做?

答:AFHTTPSessionManager的POST/GET方法返回的是task对象,直接[task cancel]即可。如:

若我想取消所有task该如何做?

答:使用如下方法。建议resetSession传入YES将session重置,否则下次网络请求会crash,并提示:“Attempted to create a task in a session that has been invalidated”

至此AFNetworking是如何工作的我们就知道了。看了源码之后不得不感叹作者真是神人,不仅优雅的给出了NSURLSession的使用范例,而且还包含了业务层面的巧妙设计。嗯,阅读源码使我快乐^_^。