前言:
最近想自己基于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以供下个方法操作
#pragma mark - NSURLSessionDataDelegate
- (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
task回调响应(代码已经添加注释):
#pragma mark - NSURLSessionTaskDelegate
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
//因为delegate的manager是weak指针,所以使用weak-strong-dance:使得回调期间manager不被释放
__strong AFURLSessionManager *manager = self.manager;
__block id responseObject = nil;
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
//Performance Improvement from #2672
NSData *data = nil;
if (self.mutableData) {
data = [self.mutableData copy];
//We no longer need the reference, so nil it out to gain back some memory.
self.mutableData = nil;
}
if (self.downloadFileURL) {
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}
if (error) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, error);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
} else {
//回调没有错误
dispatch_async(url_session_manager_processing_queue(), ^{
NSError *serializationError = nil;
//反序列化
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}
if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}
if (serializationError) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
}
/*
1,如果用户指定了group则向该group中异步提交回调(*请求发出是顺序同步执行,但是回调的执行是异步操作,因为这样更快啊)
2,如果用户未指定group则使用Manager本身的默认私有group进行回调任务处理
3,无论是默认的私有group还是用户自己的group,任务回调都是在主线程中执行的
*/
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
// 最终的回调结果
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, serializationError);
}
//
dispatch_async(dispatch_get_main_queue(), ^{
// 调主线程发出通知,跨层数据流通会考虑使用。。。反正我没用过这个通知
// 此处任务完成的通知只是为了UIKit中的一些类别中拿到回调。userInfo字段并没有返回给外部,而是给AFN的UIKit用的。当然我们可以用这个通知来获取到。
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
});
}
}
复制代码
至此一次请求的发生与回调完成