优秀iOS三方框架解析一(AFNetWorking)
一、结构
AFNetworking
是iOS最常用的网络框架,虽然系统也有NSURLSession
,但是我们一般不会直接用它。AFNetworking经过了三个大版本,现在用的大多数都是3.x的版本。
AFNetworking3.X
的构成很简单,主要就四部分,除此之外还有一些基于UIKit
的Category
。
- Manager : 负责处理网络请求的两个Manager,主要实现都在AFURLSessionManager中。
- Reachability : 网络状态监控。
- Security : 处理网络安全和HTTPS相关的。
- Serialization : 请求和返回数据的格式化器。
二、核心逻辑
先来看一下如何使用 AFNetworking 发送一个 请求:
NSURL *url = [[NSURL alloc] initWithString:@"https://news-at.zhihu.com"];
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:url];
[manager GET:@"api/4/news/latest" parameters:nil progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"%@" ,responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@", error);
}];
首先使用一个 URL,通过调用 -initWithBaseURL:
方法创建了一个 AFHTTPSessionManager
的实例,然后再调用-GET:parameters:progress:success:failure:
方法发起请求。
-initWithBaseURL:
方法的调用栈如下:
- [AFHTTPSessionManager initWithBaseURL:]
- [AFHTTPSessionManager initWithBaseURL:sessionConfiguration:]
- [AFURLSessionManager initWithSessionConfiguration:]
- [NSURLSession sessionWithConfiguration:delegate:delegateQueue:]
- [AFJSONResponseSerializer serializer] // 负责序列化响应
- [AFSecurityPolicy defaultPolicy] // 负责身份认证
- [AFNetworkReachabilityManager sharedManager] // 查看网络连接情况
- [AFHTTPRequestSerializer serializer] // 负责序列化请求
- [AFJSONResponseSerializer serializer] // 负责序列化响应
AFURLSessionManager
是 AFHTTPSessionManager
的父类, AFURLSessionManager
负责创建和管理 NSURLSession
的实例,管理 AFSecurityPolicy
和初始化 AFNetworkReachabilityManager
,来保证请求的安全和查看网络连接情况,它有一个 AFJSONResponseSerializer
的实例来序列化 HTTP 响应。
AFHTTPSessionManager
有着自己的 AFHTTPRequestSerializer
和 AFJSONResponseSerializer
来管理请求和响应的序列化,同时依赖父类实现发出 HTTP 请求、管理 Session 这一核心功能。
-GET:parameters:progress:success:failure:
方法的调用栈:
- [AFHTTPSessionManager GET:parameters:process:success:failure:]
- [AFHTTPSessionManager dataTaskWithHTTPMethod:parameters:uploadProgress:downloadProgress:success:failure:] // 返回一个 NSURLSessionDataTask 对象
- [AFHTTPRequestSerializer requestWithMethod:URLString:parameters:error:] // 返回 NSMutableURLRequest
- [AFURLSessionManager dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:]
- [NSURLSession dataTaskWithRequest:]// 返回一个 NSURLSessionDataTask 对象
- [AFURLSessionManager addDelegateForDataTask:uploadProgress:downloadProgress:completionHandler:]
- [AFURLSessionManagerTaskDelegate init]
- [AFURLSessionManager setDelegate:forTask:] // 为每个 task 创建一个对应的 delegate
- [NSURLSessionDataTask resume]
发送请求的核心在于创建和启动一个 data task,AFHTTPSessionManager
只是提供了 HTTP 请求的接口,内部最终还是调用了父类 AFURLSessionManager
来创建 data task
(其实也就是通过 NSURLSession
创建的 task
),AFURLSessionManager
中会为每个 task 创建一个对应的 AFURLSessionManagerTaskDelegate
对象,用来处理回调。
在请求发起时有一个序列化的工具类 AFHTTPRequestSerializer
来处理请求参数。
请求回调时的方法调用栈:
- [AFURLSessionManager URLSession:task:didCompleteWithError:]
- [AFURLSessionManagerTaskDelegate URLSession:task:didCompleteWithError:]
- [AFJSONResponseSerializer responseObjectForResponse:data:error:] // 解析 JSON 数据
- [AFHTTPResponseSerializer validateResponse:data:] // 验证数据
- [AFURLSessionManagerTaskDelegate URLSession:task:didCompleteWithError:]_block_invoke_2.150
- [AFHTTPSessionManager dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:]_block_invoke
AFURLSessionManager 在代理方法中收到服务器返回数据的后,会交给 AFURLSessionManagerTaskDelegate 去处理,接着就是用 AFJSONResponseSerializer 去验证和解析 JSON 数据,最后再通过 block 回调的方式返回最终结果。
三、AFURLSessionManager
AFURLSessionManager 是 AFHTTPSessionManager 的父类,主要有以下几个功能:
- 负责创建和管理 NSURLSession
- 管理 NSURLSessionTask
- 实现 NSURLSessionDelegate 等协议中的代理方法
- 使用 AFURLSessionManagerTaskDelegate 管理上传、下载进度,以及请求完成的回调
- 将整个请求流程相关的组件串联起来
- 负责整个请求过程的线程调度
- 使用 AFSecurityPolicy 验证 HTTPS 请求的证书
1. 线程
一般调用 AFNetworking 的请求 API 时,都是在主线程,也是主队列。然后直到调用 NSURLSession 的 -resume 方法,一直都是在主线程。
在 AFURLSessionManager 的初始化方法中,设置了 NSURLSession 代理回调线程的最大并发数为 1,因为就像 NSURLSession 的 -sessionWithConfiguration:delegate:delegateQueue: 方法的官方文档中所说的那样,所有的代理方法回调都应该在一个串行队列中,因为只有这样才能保证代理方法的回调顺序。
NSURLSession 代理方法回调是异步的,所以收到回调时的线程模式是“异步+串行队列”,这个时候可以理解为处于回调线程。
收到代理回调后,接着在 AFURLSessionManagerTaskDelegate 的 -URLSession:task:didCompleteWithError: 方法中,异步切换到 processing queue 进行数据解析,数据解析完成后再异步回到主队列或者自定义队列。
2. AFURLSessionManagerTaskDelegate
AFURLSessionManager 中几乎实现了所有的 NSURLSession 相关的协议方法
- NSURLSessionDelegate
- NSURLSessionTaskDelegate
- NSURLSessionDataDelegate
- NSURLSessionDownloadDelegate
但是AFURLSessionManager 中实现的这些代理方法都只是做一些非核心逻辑的处理,每个代理方法中都回调了一个自定义逻辑的 block,如果 block 被赋值了,那么就调用它。
AFURLSessionManager 把最核心的代理回调处理交给 AFURLSessionManagerTaskDelegate 类去实现了,AFURLSessionManagerTaskDelegate 可以根据对应的 task 去进行上传、下载进度回调和请求完成的回调处理:
- URLSession:task:didCompleteWithError:
- URLSession:dataTask:didReceiveData:
- URLSession:downloadTask:didFinishDownloadingToURL:
AFURLSessionManager 通过属性 mutableTaskDelegatesKeyedByTaskIdentifier (一个 NSDictionary 对象)来存储并管理每一个 NSURLSessionTask 所对应的 AFURLSessionManagerTaskDelegate,它以 taskIdentifier 为键存储 task。在请求最终完成后,又将 AFURLSessionManagerTaskDelegate 移除。
image.png
3. NSProgress
AFURLSessionManagerTaskDelegate 借助了 NSProgress 这个类来实现进度的管理,NSProgress 是 iOS 7 引进的一个用来管理任务进度的类,可以表示一个任务的进度信息,我们还可以对其进行开始 暂停、取消等操作,完整的对应了 task 的各种状态。
AFURLSessionManagerTaskDelegate 通过 KVO 监听 task 的进度更新,来同步更新 NSProgress 的进度数据。同时,还用 KVO 监听了 NSProgress 的 fractionCompleted 属性的变化,用来更新最外面的进度回调 block,回调时将这个 NSProgress 对象作为参数带过去。
另一方面,AFURLSessionManagerTaskDelegate 中还分别对下载和上传的 NSProgress 对象设置了开始、暂停、取消等操作的 handler,将 task 跟 NSProgress 的状态关联起来。这样一来,就可以通过控制 NSProgress 对象的这些操作就可以控制 task 的状态。
四.AF2.0 与 3.0的不同
4.1 AFNetworking3.0为何弃用了NSURLConnection
NSURLSession提升了网络连接速度
2015年,RFC文档(RFC文档是一系列关于Internet的技术资料汇编,早期为ARPANET)编号7540正式发表了下一代HTTP协议HTTP/2,是1999年发布HTTP/1.1以来的首个更新。相对于前一个版本,HTTP/2以快著称。加载同样一张图片,新的协议所用时间是旧协议的四分之一。
根据2015的WWDC Session711,我们知道iOS9+,NSURLSession开始正式支持HTTP/2,也就意味着你的网络连接速度可以提升不少。更人性化更优秀的API设计,HTTP/2的支持,成为了开发者摒弃NSURLConnection的理由。
Session采用了共享,而非每次都新建
事实上在HTTP/0.9 ,HTTP/1.0协议的时代,每次HTTP的请求,都需要先经过TCP的连接,而后才能开始HTTP的请求。那么,为了让我们的请求更快,避免每次都产生一个TCP三次握手,成了一个优化的选项。于是在HTTP/1.1中共享的Session将会复用TCP的连接,这样就避免了每次操作都开启一个TCP三次握手的时间浪费,即加速了网络请求时间。
通过查看文档,我们可以知道iOS对同一个IP的服务器的最大并发数为4,OSX为6,但是如果没有共享Session,则可能会超过这个数。
4.2 AFNetworking3.0后为什么不再需要常驻线程?
AF2.x为什么需要常驻线程?
开辟一条子线程,设置runloop使线程常驻。所有的请求在这个线程上发起、同时也在这个线程上回调。
首先,每一个请求对应一个AFHTTPRequestOperation实例对象(以下简称operation),每一个operation在初始化完成后都会被添加到一个NSOperationQueue中。
由这个NSOperationQueue来控制并发,系统会根据当前可用的核心数以及负载情况动态地调整最大的并发 operation 数量,我们也可以通过setMaxConcurrentoperationCount:方法来设置最大并发数。注意:并发数并不等于所开辟的线程数。具体开辟几条线程由系统决定。
也就是说此处执行operation是并发的、多线程的。
最后再来小结一下为什么AF2.x需要一条常驻线程:
首先需要在子线程去start connection,请求发送后,所在的子线程需要保活以保证正常接收到 NSURLConnectionDelegate 回调方法。如果每来一个请求就开一条线程,并且保活线程,这样开销太大了。所以只需要保活一条固定的线程,在这个线程里发起请求、接收回调。
AF3.x为什么不再需要常驻线程?
NSURLConnection的一大痛点就是:发起请求后,这条线程并不能随风而去,而需要一直处于等待回调的状态。
苹果也是明白了这一痛点,从iOS9.0开始 deprecated 了NSURLConnection。 替代方案就是NSURLSession。
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
同时还要注意一下,指定的用于接收回调的Queue的maxConcurrentOperationCount设为了1,这里目的是想要让并发的请求串行的进行回调。
为什么要串行回调?
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task);
AFURLSessionManagerTaskDelegate *delegate = nil;
[self.lock lock];
//给所要访问的资源加锁,防止造成数据混乱
delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
[self.lock unlock];
return delegate;
}
这边对 self.mutableTaskDelegatesKeyedByTaskIdentifier 的访问进行了加锁,目的是保证多线程环境下的数据安全。既然加了锁,就算maxConcurrentOperationCount不设为1,当某个请求正在回调时,下一个请求还是得等待一直到上个请求获取完所要的资源后解锁,所以这边并发回调也是没有意义的。相反多task回调导致的多线程并发,还会导致性能的浪费。
所以,为什么在AF3.0中。要设置 self.operationQueue.maxConcurrentOperationCount = 1; 呢?AF2.0却不需要?
解答:功能不一样:AF3.0的operationQueue是用来接收NSURLSessionDelegate回调的,鉴于一些多线程数据访问的安全性考虑,设置了maxConcurrentOperationCount = 1来达到串行回调的效果。
而AF2.0的operationQueue是用来添加operation并进行并发请求的,所以不要设置为1。