代码轮廓
今天开始准备把最新AFNetworking
的代码通读一下,做一下笔记的记录。打开代码,我们从接口文件开始切入,顺路看一下代码的目录结构是怎么样的。AFNetworking
中对外的文件是AFNetworking.h
。
//! Project version number for AFNetworking.
FOUNDATION_EXPORT double AFNetworkingVersionNumber;
//! Project version string for AFNetworking.
FOUNDATION_EXPORT const unsigned char AFNetworkingVersionString[];
代码开始向外界暴露了两个变量这两个变量告诉外界自己的版本信息,便于用于分析版本对于自己的代码的影响。正常逻辑中可能用不太上,但是一旦发生一些跟版本相关的复杂又比较难查找的问题,那么用户可能发现版本信息的用途还是很大的。
接下来的代码AFNetworking.h
将AFNetworking
中用户可能用到的头文件依次暴露了出去,大家可以根据自己的需求依次查阅,这里不再赘余。我们直接打开文件目录看一下代码的结构。
现在的代码中有两个核心的目录,AFNetworking
和UIKit+AFNetworking
。AFNetworking
包含与通信相关的核心代码,UIKit+AFNetworking
中是一些跟UI相关的应用代码,即这个目录中基本上是对AFNetworking
代码的一些封装,给用户一些方便的接口来使用AFNetworking
目录中的通信代码。我们代码的阅读从AFNetworking
这个目录开始。
AFCompatibilityMacros.h
AFHTTPSessionManager.h
AFHTTPSessionManager.m
AFNetworkReachabilityManager.h
AFNetworkReachabilityManager.m
AFSecurityPolicy.h
AFSecurityPolicy.m
AFURLRequestSerialization.h
AFURLRequestSerialization.m
AFURLResponseSerialization.h
AFURLResponseSerialization.m
AFURLSessionManager.h
AFURLSessionManager.m
从目录上看,我们可以将其分为这几个功能模块:
- 网络监听模块(AFNetworkReachabilityManager)
- 网络安全策略模块(AFSecurityPolicy)
- 网络通信信息序列化/反序列化模块(AFURLRequestSerialization/AFURLResponseSerialization)
- 网络通信模块(AFURLSessionManager、AFHTTPSessionManager)
AFNetworking
是基于NSURLSession
来做的,它的核心类是AFURLSessionManager
,而AFHTTPSessionManager
继承于AFURLSessionManager
,它封装了一些Http的方法和逻辑处理,其实归根结底会把真正的请求交给AFURLSessionManager
来做。而AFURLSessionManager
则是将于NSURLSession
做了一系列的封装,最终将请求交给NSURLSession
,然后从NSURLSession
的代理回调中拿到数据传递给上层。下面是AFNetworking
的一个简单的架构框图,等到后面我们试着画出AFNetworking
的类图,会更复杂一点,目前从最顶层来看架构图就是下图所示的情况:
从一个请求看过去
AFHTTPSessionManager *client = [[AFHTTPSessionManager alloc] init];
[client GET:@"http://localhost" parameters:nil headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
我们通过AFHTTPSessionManager初始化了一个client的实例,紧接着调用一个方法发送了一个请求,调用这个方法的时候,我们发送了自己的需要请求数据的接口地址,然后可以填写一些参数,请求的结果通过success和failure的回调传递回来。我们就从这次网络请求看过去,从初始化到发送请求的参数整合序列化,到最后收到数据已经数据序列化之后返回给回调中,来分析代码的流程是怎样的。
首先是这个初始化方法,我们打开代码点进去看:
- (instancetype)init {
return [self initWithBaseURL:nil];
}
- (instancetype)initWithBaseURL:(NSURL *)url {
return [self initWithBaseURL:url sessionConfiguration:nil];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
return [self initWithBaseURL:nil sessionConfiguration:configuration];
}
- (instancetype)initWithBaseURL:(NSURL *)url
sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
self = [super initWithSessionConfiguration:configuration];
if (!self) {
return nil;
}
// 若果url path的长度大于0且url不是以斜杠结尾的,那么给url的末尾添加斜杠
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}
self.baseURL = url;
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
return self;
}
- 初始化方法最终都调用了
- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration
这个方法,而这个方法调用了父类的方法- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration
- 将url赋值给
self.baseURL
- 给
self.requestSerialer
和self.responseSerializer
赋值。
self.baseURL
后面的代码会遇到,序列化和反序列化的类我们也放在后面再看。我们直接来到父类中看一下[super initWithSessionConfiguration:configuration];
的方法。
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;
self.operationQueue = [[NSOperationQueue alloc] init];
//并发队列的最大线程数设为1
self.operationQueue.maxConcurrentOperationCount = 1;
self.responseSerializer = [AFJSONResponseSerializer serializer];
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
#if !TARGET_OS_WATCH
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
__weak typeof(self) weakSelf = self;
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
__strong typeof(weakSelf) strongSelf = weakSelf;
for (NSURLSessionDataTask *task in dataTasks) {
[strongSelf addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
}
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
[strongSelf addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
}
for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
[strongSelf addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
}
}];
return self;
}
- 初始化了一些属性
- 初始化了一个操作队列,最大并发数设置为1
这个比较让人疑惑,为啥将并发队列最大并发数设置为1了,那么我们就看一下这个队列里面都做了什么,应该就能找到其中的原委了。
- (NSURLSession *)session {
@synchronized (self) {
if (!_session) {
_session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
}
}
return _session;
}
即这个操作队列是处理NSURLSession代理返回的,那么我们再看看代理返回里面做了什么。
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task);
AFURLSessionManagerTaskDelegate *delegate = nil;
[self.lock lock];
delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
[self.lock unlock];
return delegate;
}
首先都是通过一个task找到一个AFURLSessionManagerTaskDelegate类型的对象,这个delegate对象存在mutableTaskDelegatesKeyedByTaskIdentifier这个字典里面,为了保证这个字典的多线程安全,对他的访问都加了一把锁,所以即便是代码回调的操作队列最大并发数不设置为1,因为锁的存在,下一个请求还是得等待一直到上个请求获取完所要的资源后解锁,所以这边并发回调是没有意义的。相反如果多任务的去回调处理,由于锁的存在反而会增加性能的消耗。
- 紧接着调用了下面这个方法
__weak typeof(self) weakSelf = self;
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
}];
获取task然后对task的一些属性做了置空的操作,(这有个毛用???)内心的潜台词是不是这样的,在一个初始化方法里,会拿到task吗?好吧,我只能相信大神,然后去网上找一下答案了。为什么在initWithSessionConfiguration中执行self.session getTasksWithCompletionHandler? #3499 AFNetworking的issue中有这个答案,希望对大家有帮助。是为了后台返回的时候可能重新初始化,然后后台有一些任务未完成,导致崩溃。
初始化方法小结
写到这里初始化的方法就读完了,我们简单回顾一下,其实主要的目的就是生成一个NSURLSession,围绕这它我们做了sessionConfiguration,回调代理队列的初始化,反序列化responseSerializer的初始化,安全策略方针securityPolicy的初始化,网络监听器reachabilityManager的初始化,容器mutableTaskDelegatesKeyedByTaskIdentifier的初始化。其实都是为了发送请求和接收回调提前做好准备。