AFNetworking3.1源码解读<一>

现阶段几乎所有的App都有联网的需求,而AFNetworking则是一款优秀的HTTP网络通信库,Github上star数已过2W可以说是变成了iOS App标配三方库。AFNetworking3.0以前是基于NSURLConnetion,3.0以后才用Apple最新的网络库NSURLSession,本文仅用于记录学习AFNeworking3.1源码的心得。

AFNetworking3.1代码结构非常清晰,通过CocoaPods下载源码后可以看出主要由4部分组成:NSURLSession、Reachability、Security、Serialization。本篇先学习NSURLSession。

Session

AFNetworking中与Session相关的有2个类,分别为AFHTTPSessionManager和AFURLSessionManger。其中AFHTTPSessionManager是AFURLSessionManager的子类,主要提供了一些方便生成HTTP请求的接口;AFURLSessionManager则用于创建和管理NSURLSession对象。如果项目中与WebService交互的特别频繁,那么建议子类化AFHTTPSessionManager然后提供一个类方法来返回一个单例对象,该单例对象配置了项目中通用的鉴权、缓存之类的配置,此方法还可以统一处理异常行为。

初始化

  1. NSURLSession配置:NSURLSession默认配置为defaultSessionConfiguration,该配置可以共享全局认证、缓存、Cookie存储策略。
  2. 解析线程:创建了一个串行的NSOperationQueue用于处理Session的回调。
  3. 多线程安全:为了使Add Request和Remove Request线程安全,创建了一个互斥锁NSLock *lock
  4. 响应格式:默认是JSON格式。
  5. 安全策略:默认是AFSSLPinningModeNone

核心组件通信

一条HTTP请求创建、发送、响应接收需用到NSURLSession和Serialization这2个核心组件,整个通信过程如下图所示。
AFNetworking核心组件通信

AFURLSessionManagerTaskDelegate是AFURLSessionManager的委托,处理实现请求进度、响应回调等与业务层直接交互的接口。AFURLSessionManager直接实现NSURLSessionDelegate系列回调接口,在实现

- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error

- (void)URLSession:(__unused NSURLSession *)session dataTask:(__unused NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location

这几个接口时委托给AFURLSessionManagerTaskDelegate处理。

关键技术

响应->请求映射

一般编写的网络请求都是异步的,当发送了多条网络请求后,如何识别出接收到的响应是哪个请求的结果呢?AFNetworking的做法是在内部维护一个NSMutableDictionary,key是NSURLSessionTask的taskIdentifier(由NSURLSession框架维护,确保唯一),value是AFURLSessionManagerTaskDelegate(该委托持有请求的completionHandler、progress等),在发送请求时添加taskIdentifier和相应的AFURLSessionManagerTaskDelegate,所以当AFNetworking接收到NSURLSession的finish回调时通过taskIdentifier查找到相应的AFURLSessionManagerTaskDelegate,然后AFURLSessionManagerTaskDelegate根据responseSerialization解析出响应数据返回给回调者。

说到这儿你可能会疑惑taskIdentifier是如何确定的,更底层的实现是如何的呢?想要弄明白这些事情我们先补充点TCP相关的知识(HTTP是建立在TCP传输层之上的),一条TCP连接需要由6个元素确定:源mac地址、源ip地址、源端口号、目标mac地址、目标ip地址、目标端口号。当我们在一个App中向同一个Web服务器发送多条请求(即便是同一个Request发送了多次)时,就会创建多条TCP连接,这些连接的区别只在于源端口号不同,而源端口号默认是内核分配的,内核是知道哪个App(precess)申请了创建了哪些TCP连接的,所以当创建一个NSURLSessionTask时内核会先创建一条tcp连接,然后根据相关策略生成一个类端口号的标示返回赋值给taskIdentifier。大体意思如下图所示。
Request->Response

因为可以确定发送请求时的tcp端口号,所以响应到来时根据tcp端口号就可以确定是哪个请求的响应了。可以看出图中的ResponseA是Request2的响应,ResponseB是Request1的响应。

总而言之就是每条Http请求都有自己的tcp连接,相当于有独立的传输通道,所以就可以根据响应的通道标示来找出是哪个请求的响应。

请求进度progress实现

请求progress主要由AFURLSessionManagerTaskDelegate类实现,该类有两个NSProgress属性,分别为uploadProgress和downloadProgress,在创建请求时AFURLSessionManagerTaskDelegate让uploadProgress和downloadProgress使用KVO监听了NSURLSessionTask的属性(countOfBytesReceived、countOfBytesExpectedToReceive、countOfBytesSent、countOfBytesExpectedToSend),所以在接收到请求数据后progress能实时响应。

三条线程

  • 处理系统网络响应数据线程:NSOperationQueue创建,NSOperationQueue是在初始化AFURLSessionManager时创建的。

  • 解析ResponseData线程:url_session_manager_processing_queue,AFNetworking私有队列,并行队列。

  • 回调业务请求线程:于业务completionQueue一致,如果没指定则为主线程。

__block修饰符

__block表示为引用传递,共享内存,在AFNetworking中有如下代码:

__block NSURLSessionDataTask *dataTask = nil;
    dataTask = [self dataTaskWithRequest:request
                          uploadProgress:uploadProgress
                        downloadProgress:downloadProgress
                       completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        if (error) {
            if (failure) {
                failure(dataTask, error);
            }
        } else {
            if (success) {
                success(dataTask, responseObject);
            }
        }
    }];

如果上述block NSURLSessionDataTask *dataTask = nil没用block修饰的话,则failure(dataTask, error)和success(dataTask, responseObject)中dataTask始终为nil,因为block捕获了dataTask = nil不会再改变,根本原因是block默认是值拷贝,所以此处需要增加__block。