正文
前言:
最近想自己基于NSURLSession封一个网络框架,但是试了几次之后发现总是考虑的不全面,于是决定好好读下AFN的源码再进行设计,不得不说,AFN考虑的东西真的很全面。站在巨人的肩膀上?虽然可能自己写的框架不如AFN好用,但是可以增强自己的理解于认识,更好的理解运行机制。
于是,决定以一次最简单的HTTP_data_ Task 请求为例,详细说下AFN的请求步骤与机制:
(代码注释已添加)
AFURLSessionManager(最重要的类)
AFHTTPSessionManager是使用AFURLSessionManager进行HTTP请求的便捷方法的子类,其实根本的task请求还是从AFURLSessionManager中的方法发出的,因此就跳过AFHTTPSessionManager的Request封装过程,直接从AFURLSessionManager的方法始:
先说下主要的属性,后面的方法中会用到
.h:
常用属性:
1.维护一个NSURLSession:
@property (readonly, nonatomic, strong) NSURLSession *session;
2.NSURLSession的OperationQueue:
就是NSURLSession的回调所在的队列,默认是子线程的串行队列,也无法改变,NSURLSession本来回调就是在子线程中进行的。
@property (readonly, nonatomic, strong) NSOperationQueue *operationQueue;
这会在AFURLSessionManager的初始化中进行设定。
//会在AFN内部初始化一个操作队列(根据后面的maxcount,该队列为串行队列)
self.operationQueue = [[NSOperationQueue alloc] init];
//当前请求最大并发数为1(苹果规定,iOS端一个IP的最大访问进程数目是4)
//最大并发数为1,也就证明了AFN内部的task是串行执行的
self.operationQueue.maxConcurrentOperationCount = 1;
//维护AFNURLSessionManager内部的NSURLSession
//设置回调的队列
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
3.completionQueue:处理请求回调的调度队列
如果NULL(默认),则使用主队列。
这也就是AFN的block的回调默认都是在主线程中进行的原因。
此时Session的内部的回调在内部的匿名子线程进行,然后AFN会在子线程中吊起主线程,传入successBlock或者failureBlock,在主线程中进行解析。
/**
The dispatch queue for `completionBlock`. If `NULL` (default), the main queue is used.
*/
@property (nonatomic, strong, nullable) dispatch_queue_t completionQueue;
4.调度组completionGroup
如果是用户不指定group,NULL(默认),则使用AFN内部的私人派遣组。
@property (nonatomic, strong, nullable) dispatch_group_t completionGroup;
5.session当前的task:
//全部请求task
@property(readonly,nonatomic,strong)NSArray *tasks;
//data请求task
@property(readonly,nonatomic,strong)NSArray *dataTasks;
//上传task
@property(readonly,nonatomic,strong)NSArray *uploadTasks;
//下载task
@property(readonly,nonatomic,strong)NSArray *downloadTasks;
6.是否重新创建任务
attemptsToRecreateUploadTasksForBackgroundSessions
@property (nonatomic, assign) BOOL attemptsToRecreateUploadTasksForBackgroundSessions
这个属性非常重要,在iOS7中存在一个bug,在创建后台上传任务时,有时候会返回nil,所以为了解决这个问题,AFNetworking遵照了苹果的建议,在创建失败的时候,会重新尝试创建,次数默认为3次,所以你的应用如果有场景会有在后台上传的情况的话,记得将该值设为YES,避免出现上传失败的问题,默认是NO。
.m
方法的具体请求步骤:
最常用的方法入口:AFHTTPSessionManager的GET,POST请求什么的,最后基本都是走AFNURLSessionManager的这个入口:
//request已经在AFNHTTPSessionManager中封装好了
- (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
具体实现:
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
//收到最终处理完的task
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
//提交请求
[dataTask resume];
return dataTask;
}
进入dataTaskWithHTTPMethod方法,接收task,并为task加上处理并返回task
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
return nil;
}
__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);
}
}
}];
return dataTask;
}
再进入
dataTaskWithRequest:uploadProgress:downloadProgress: completionHandler:
方法生成task并返回:
.h中的属性:
dataTask
、
uploadTask
、
downloadTask
实际上都是completionHanlder block返回出来的,但是我们知道网络请求是delegate返回结果的,AF内部做了巧妙的操作,他对每个task都增加代理设置
- (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;
}
进入
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask:uploadProgress:downloadProgress:completionHandler:
为task添加delegate,并将block赋值给delegate的block属性
- (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是weak,跟session的delegate不同。session的delegate很特殊
delegate.manager = self;
//将处理的block赋值给delegate,目的是在Session的delegate回调中进行主线程回调block,如果AFN指定了操作队列,则在指定的操作队列中进行回调
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
// 设置task的delegate
[self setDelegate:delegate forTask:dataTask];
// 设置上传和下载进度回调
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
然后delegate对象利用KVO将task对一些方法进行监听,注册的通知是
task
的
suspend
和
resume
,监听到变化时,delegate扔出block
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
// 断言
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
// task使用kvo对一些方法监听,返回上传或者下载的进度
[delegate setupProgressForTask:task];
// sessionManager对暂停task和恢复task进行注册通知
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
关于
setupProgressForTask
再往下,主要是对task和progress设置监听,以及一些异常处理操作,这里不再进行继续深入了。
===============================================
#####至此,一次
AFNURLSessionManager
的Task请求完成
请求的回调还是
NSURLSession
的那些代理
【NSURLSessionDataDelegate,NSURLSessionDataDelegate,NSURLSessionDownloadDelegate'】,只是AFN把他们封装了,加上了必要的处理,使得我们直接在
AFNHTTPSessionMAnager
的completionHandler block就可以完成回调的处理了.
####核心的回调方法有三个,依次是:
-
接受到数据的回调:didReceiveData
-
task完成的回调
-
下载完成的回调(只有downloadTask才会触发)
##下面以普通的data_Task为例子:
###首先是
didReceiveData
没什么要说的,就是拼接data以供下个方法操作
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
self.downloadProgress.totalUnitCount = dataTask.countOfBytesExpectedToReceive;
self.downloadProgress.completedUnitCount = dataTask.countOfBytesReceived;
[self.mutableData appendData:data];
}
###再是NSURLSessionTaskDelegate的
URLSession:task:didReceiveChallenge:completionHandler:
说明:首先会把返回的数据赋给一个局部data,然后将全局的mutableData置空,这样就可以保证下次请求的数据是重新加载。这里区分了下载和普通的数据返回,如果是下载的话,直接下载到指定文件路径中,如果用户指定这个路径的话,userInfo字典里面存的就是下载路径,否则,存的是下载数据。
然后就是判断有没有错误,错误的话把错误返回,返回值task.response, responseObject此处是空的,然后就是error。最后在主线程中发送通知,为当前task的userInfo,注意此处的userInfo只是用来做通知信息的。而我们平时用的时候不会用通知来获取请求成功的回调,这个通知是为了AFNetworking中的UIKit封装部分服务的。(反正我不喜欢用AFN的UIKit)
然后就是成功的回调,异步请求在singleton队列中,这里对队列的生成都加了singleton的保护。这里通过responseSerializer 对结果数据进行转化成对应的格式(参考ADN的Serialization部分官方文档,[http://cocoadocs.org/docsets/AFNetworking/3.1.0/Classes/AFURLSessionManager.html#//api/name/dataTaskWithRequest:completionHandler: ]里面讲解如何转化的),如果是下载的话,responseObject直接赋值成downloadFileURL,也就是下载的话,回调中只会有下载的目标地址。然后就是对userInfo的AFNetworkingTaskDidCompleteSerializedResponseKey(序列化响应结果)、AFNetworkingTaskDidCompleteErrorKey(序列化过程中的错误信息)进行赋值,不得不说AFNetworking对各个部分的情况都返回回去了,做的很详细。
然后就是调用回调block:completionHandler:
返回值task.response也就是完整的返回头信息以及返回的状态码。
responseObject是返回的数据或者是下载的目标地址。
serializationError注意这个地方的错误是序列化的错误,也就是此处如果对返回数据序列化产生错误,也会照样返回成功回调,只是回调结果会是序列化的错误。
最后还是一样的发送通知给AFN的封的UIKit