AF版本基于3.0,下面将从使用切入开始分析。
1.使用
例子:
AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
session.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html",@"application/json", @"text/json" ,@"text/javascript", nil];
session.responseSerializer = [AFHTTPResponseSerializer serializer];
[session GET:@"https://www.baidu.com"
parameters:nil
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"请求成功");
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"请求失败");
}];
2 代码分析
2.1 [AFHTTPSessionManager manager]
AFHTTPSessionManager
is a subclass of AFURLSessionManager
with convenience methods for making HTTP requests. When a baseURL
is provided, requests made with the GET
/ POST
/ et al. convenience methods can be made with relative paths.
我们加入断点查看一下manager的初始化调用栈如下图:
进入到AFURLSessionManager
中的manager中分析:
// 1.设置全局的网络行为策略的配置
self.sessionConfiguration = configuration;
// 2.设置请求的队列,默认最大的并发数为1
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
// 3.根据configuration,operationQueue初始化全局的NSURLSession
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
// 4.设置网络请求响应的数据解析实例
self.responseSerializer = [AFJSONResponseSerializer serializer];
// 5.设置网络请求安全策略实例(后续针对这个做具体说明)
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
// 6.初始化全局的网络状态监听的实例
#if !TARGET_OS_WATCH
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
// 7.将taskId与其delegate绑定,实现解耦,后续对整个过程做分析
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
2.2.全局的网络行为策略的配置 NSURLSessionConfiguration
An NSURLSessionConfiguration object defines the behavior and policies to use when uploading and downloading data using an NSURLSession object. When uploading or downloading data, creating a configuration object is always the first step you must take. You use this object to configure the timeout values, caching policies, connection requirements, and other types of information that you intend to use with your NSURLSession object.
简单罗列为一下几点:
- NSURLSessionConfiguration 可以控制网络请求中的缓存策略,超时设置等。
- 如果需要更改网络请求的行为策略必须重新在更改NSURLSessionConfiguration后再创建一个新的NSURLSession对象。
2.3 请求的方法GET:parameters:process:success:failure:
2.3.1
具体调用栈如下图:
这里引入了一个新的类:NSURLSessionDataTask
,继承了NSURLSessionTask
,我们看看官方的说明:
The NSURLSessionTask class is the base class for tasks in a URL session. Tasks are always part of a session; you create a task by calling one of the task creation methods on an NSURLSession object. The method you call determines the type of task.
URL sessions provide three types of tasks: data tasks, upload tasks, and download tasks. These tasks are instances of the NSURLSessionDataTask, NSURLSessionUploadTask, NSURLSessionDownloadTask, NSURLSessionStreamTask subclasses of NSURLSessionTask, respectively.
简单罗列为一下几点:
- NSURLSessionTask是官方提供的几种网络任务类的基类。
- 官方提供了三种任务处理的子类:简单数据处理任务类,上传任务类,下载任务类。
介绍了这其中使用到的核心类NSURLSessionTask,我们由底层向上看一下具体的调用流程。
2.3.2 [AFHTTPSessionManager dataTaskWithHTTPMethod:...]
// 1.通过全局配置的requestSerializer 初始化一个请求的实例
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
// 2.根据请求的实例再初始化一个task的实例
__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);
}
}
}];
2.3.2 task与delegate的绑定[AFURLSessionManager addDelegateForDataTask:...]
这里是初始化一个请求task的基本步骤:
// 1.先根据请求信息初始化一个task的实例
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
- (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
{
// 2.初始化一个任务的代理
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
delegate.manager = self;
// 此处赋值了请求完成的回调,后续会用到
delegate.completionHandler = completionHandler;
// 2.利用全局的字典存储绑定信息,key为taskId,value为代理的实例
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
// 1.绑定taskId与代理self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
// 2.使用kvo对一些方法监听,返回上传或者下载的进度
[delegate setupProgressForTask:task];
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
2.3.3 URLSession:task:didCompleteWithError:
当请求收到了响应后,会触发该回调,我们分析一下他具体的处理。
// AFURLSessionManager.m
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
// 1.根据task获取绑定的代理实例
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// delegate may be nil when completing a task in the background
if (delegate) {
// 2.统一处理
[delegate URLSession:session task:task didCompleteWithError:error];
[self removeDelegateForTask:task];
}
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}
// AFURLSessionManagerTaskDelegate.m
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
if (error) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
// 1.iOS网络框架返回的错误信息处理
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(), ^{
// 2.请求成功后需要用响应的数据解析类实例处理返回数据,同样如果出现错误则回调上层
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;
}
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
// 3.绑定task与处理的数据代理时,传入代理的完成的回调
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, serializationError);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
});
}
#pragma clang diagnostic pop
}
3 调试中遇到的问题的解决
1.请求收到响应:response Code=-1016 "Request failed: unacceptable content-type: text/html"
代码段:
AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
[session GET:@"https://www.baidu.com"
parameters:nil
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"请求成功");
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"请求失败");
}];
解决:响应加入对text/html
格式的支持
session.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html",@"application/json", @"text/json" ,@"text/javascript", nil];
2.Code=3840 "JSON text did not start with array or object and option to allow fragments not set."
解决:AF默认设置的响应的解析类型为json,因此需要改变解析类型。
解决:
session.responseSerializer = [AFHTTPResponseSerializer serializer];
3.AFNetworking 与 RunLoop 之间的关系,3.0为什么不需要加入如下的段?
我们看看NSURLConnection 的官方文档描述:
These delegate methods are called on the thread that initiated the asynchronous load operation.
NSURLConnection的delegate方法需要在connection发起的线程的runloop中调用。因此,当发起connection的线程exit了,delegate自然不会被调用,请求也就回不来了。因此AF 2.X 加入了NSThread + runLoop去解决这个问题:
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
// 创建一个runloop,添加对input source的的监听
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
而AF 3.0为什么不需要做类似的处理呢?我们看看官方的说明:
Thread Safety
The URL session API itself is fully thread-safe. You can freely create sessions and tasks in any thread context, and when your delegate methods call the provided completion handlers, the work is automatically scheduled on the correct delegate queue