作者|刘小壮 字数:9029字
本文预计阅读时间:29分钟
AFNetworking
是iOS
最常用的网络框架,虽然系统也有NSURLSession
,但是我们一般不会直接用它。AFNetworking
经过了三个大版本,现在用的大多数都是3.x的版本。
AFNetworking
经历了下面三个阶段的发展:
1.0版本 : 基于
NSURLConnection
的封装。2.0版本 : 两套实现,分别基于
NSURLConnection
和NSURLSession
,是转向NSURLSession
的过渡版。3.0版本 : 基于
NSURLSession
的封装。
文件构成
AFNetworking3.X
的构成很简单,主要就四部分,除此之外还有一些基于UIKit
的Category
,但这些并不是标配。
Manager : 负责处理网络请求的两个
Manager
,主要实现都在AFURLSessionManager
中。Reachability : 网络状态监控。
Security : 处理网络安全和
HTTPS
相关的。Serialization : 请求和返回数据的格式化器。
AFURLSessionManager
在AFN3.0
中,网络请求的manager
主要有AFHTTPSessionManager
和AFURLSessionManager
构成,二者为父子关系。这两个类职责划分很清晰,父类负责处理一些基础的网络请求代码,并且接受NSURLRequest
对象,而子类则负责处理和http
协议有关的逻辑。
AFN
的这套设计很便于扩展,如果以后想增加FTP
协议的处理,则基于AFURLSessionManager
创建子类即可。子类中只需要进行很少的代码处理,创建一个NSURLRequest
对象后调用父类代码,由父类去完成具体的请求操作。
创建sessionManager
AFHTTPSessionManager
类的初始化方法中并没有太多实现代码,其内部调用的都是父类AFURLSessionManager
的initWithSessionConfiguration
方法,下面是此方法内部的一些关键代码。
在初始化方法中包含一个参数sessionConfiguration
,如果没有传入的话默认是使用系统的defaultConfiguration
,我们创建是一般都不会自定义configuration
,所以大多数都是系统的。
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
随后是NSURLSession
的初始化代码,关于NSOperationQueue
后面详细进行讲解。NSURLSession
的初始化方式有两种,一种是使用系统的共享session
,另一种是自己创建session
。AFN
选择的是创建自己的session
,并且每个请求都会创建一个独立的session
。
可以通过NSURLSession
进行连接复用,这样可以避免很多握手和挥手的过程,提高网络请求速度,苹果允许iOS
设备上一个域名可以有四个连接同时存在。但是由于AFN
的实现是每个请求都创建一个session
,所以就不能进行连接复用。
所以可以通过在外面对AFN
进行二次封装,将AFHTTPSessionManager
复用为单例对象,通过复用sessionManager
的方式,来进行连接的复用。但是这种方案对于不同的requestSerializer
、responseSerializer
等情况,还是要做特殊兼容,所以最好建立一个sessionManager
池,对于同类型的sessionManager
直接拿出来复用,否则就创建新的。
// 共享session连接池
[NSURLSession sharedSession];
// 创建新session,则不能使用共享session连接池
[NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
由于当前AFURLSessionManager
对象的所有sessionTask
请求任务,都是共享同一个回调代理的,所以AFN
为了区分每个sessionTask
,通过下面的可变字典,将所有taskDelegate
和task.taskIdentifier
的进行了一一对应,以便于很容易的对每个请求task
进行操作。
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
在初始化方法中,可以发现AFN
在创建session
后,调用了getTasksWithCompletionHandler
方法来获取当前所有的task
。但是现在刚创建session
,理论上来说是不应该有task
的。但从AFN的issues
中找到了答案issues 3499。
这是因为,在completionHandler
回调中,为了防止进入前台时,通过session id
恢复的task
导致一些崩溃问题,所以这里将之前的task
进行遍历,并将回调都置nil
。
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
for (NSURLSessionDataTask *task in dataTasks) {
[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
}
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
}
for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
}
}];
创建task
在AFURLSessionManager
中进行task
的创建,task
的类型总共分为三种,dataTask
、uploadTask
、downloadTask
,AFN
并没有对streamTask
进行处理。
AFHTTPSessionManager
在创建GET
、POST
等请求时,本质上都是调用了下面的方法或其类似的方法,方法内部会创建一个task
对象,并调用addDelegateForDataTask
将后面的处理交给AFURLSessionManagerTaskDelegate
来完成。随后会将task
返回给调用方,调用方获取到task
对象后,也就是子类AFHTTPSessionManager
,会调用resume
方法开始请求。
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {
__block NSURLSessionDataTask *dataTask = nil;
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
除了普通请求外,upload
、download
都有类似的处理。
在addDelegateForDataTask
方法中,会调用sessionManager
的setDelegate:forTask:
方法,此方法内部将task
和taskDelegate
进行了注册。由于AFN
可以通过通知让外界监听请求状态,所以在此方法中还监听了task
的resume
和suspend
事件,并在实现代码中将事件广播出去。
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
如果从AFHTTPSessionManager
的创建任务开始,按代码逻辑跟到这里,发现其实AFN3.0
的请求代码真的很简单,主要都集中在创建NSMutableURLRequest
那里,其他都依赖于NSURLSession
,因为确实NSURLSession
的API
封装程度比较好,也很好使用。
AFN3.0
的作用就是对NSURLSession
的封装性比较好,你不用去写太多重复性的代码,并且可以很容易的通过block
得到回调结果。
AFURLSessionManagerTaskDelegate
NSURLSession
的回调方法比较多,这里只针对一些关键代码进行讲解,以及梳理整体回调逻辑,不一一列举每个回调方法的作用,详细源码各位可以直接下载AFN
代码查看。
在AFURLSessionManager
中,有一个AFURLSessionManagerTaskDelegate
类比较重要,这个类和sessionTask
是一一对应的,负责处理sessionTask
请求的很多逻辑,NSURLSessionDelegate
的回调基本都转发给taskDelegate
去处理了。在NSURLSession
回调中处理了HTTPS
证书验证、下载进度之类的,没有太复杂的处理。
taskDelegate
的设计很不错,可以将代理回调任务处理对象化,也可以给AFURLSessionManager
类瘦身。比较理想的是直接将代理设置为taskDelegate
,但是由于会涉及一些AFURLSessionManager
自身的处理逻辑,所以才设计为消息传递的方式。
taskDelegate
的功能很简单,主要是NSData
数据的处理,NSProgress
上传下载进度的处理,以及通知参数的处理。在进行AFN
的下载处理时,NSData
的数据拼接、事件回调,及文件处理,都是由taskDelegate
来完成的。
下面是downloadTask
任务完成时的处理代码,其他回调代码就不一一列举了。
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
self.downloadFileURL = nil;
if (self.downloadTaskDidFinishDownloading) {
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (self.downloadFileURL) {
NSError *fileManagerError = nil;
if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
}
}
}
}
taskDelega