首页 > 代码库 > AFNetWorking VS NSURLSession
AFNetWorking VS NSURLSession
为了写这篇文章,特意写了前一篇NSURLSession,为了更好的理解,可以先看看前一篇。
本文目的在于讲解AFNetWorking比起原生的NSURLSession发生网络请求,都帮我们做了些什么呢,世人都喜欢AF,那AF到底有多漂亮,下面我会对比原生的NSURLSession揭开AF神秘的面纱,让我们一起目睹她的芳容。
我先梳理一下发起一个网络请求的步骤:
1.构建请求
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
2.创建会话
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:queue];
3.创建请求任务
NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
[task resume];
4.请求状态的跟踪(会话状态、数据任务状态、下载状态)
NSURLSessionDelegate NSURLSessionTaskDelegate NSURLSessionDataDelegate NSURLSessionDownloadDelegate
5.请求结果处理
[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]
那么AF在这五部里面都帮我们做了些什么呢,让我们一步一步来扔掉她的扇子,拉下她的纱巾:
1.构建请求
AF中的请求创建源码是这样的
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
然后它做对mutableRequest了一系列的设置:
a.属性设置:这个AF还真没有做什么,交给我们自己设置
b.请求头填写:
[mutableRequest setValue:@"" forHTTPHeaderField:@"Accept-Language"];
[mutableRequest setValue:@"" forHTTPHeaderField:@"User-Agent"];
[mutableRequest setValue:@"" forHTTPHeaderField:@"Authorization"];
[mutableRequest setValue:@"" forHTTPHeaderField:@"Content-Type"];
打印了下AF请求的请求头得到:
Accept-Language = zh-Hans-US;q=1, en-US;q=0.9,
User-Agent = test2/1.0 (iPhone; iOS 9.3; Scale/3.00)
用这个方法让我们设置Authorization更方便了
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username password:(NSString *)password{
NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
[self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"];
}
c.参数填写:
参数填写还是帮我们做了很多复杂的事情,有下面几个:
(1)url编码
(2)请求头里参数的数据类型,参数转码
//默认的
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
//json
[mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]];
//数据列表
[mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"];
[mutableRequest setHTTPBody:[NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error]];
d.上传文件的body拼接
2.创建会话
AF中创建会话的代码是这样的
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
从这几个方法的观察,就可以知道AF帮我们做了以下几点:
a.我们可以不用创建NSURL ,可以直接使用NSString;
b.不用再自己拼接URL的参数,可以直接通过字典快捷的填写;(而且参数中有中文什么的都可以直接使用,AF里面已经帮你做了url编码)
c.不用自己到代理方法中获取数据传输的进度,可以直接通过设置block,等待回调就行;
d.请求结果的回调,只不过原生的NSURLSession分类已经做到这一点了;
e.不用自己在请求体里拼接复杂的格式化的那一堆东西,上传数据可以通过block直接填写;
在我看来实现这五点已经相当厉害了,长期使用NSURLSession的人光看见AF这样的请求方法就能惊呆了,这减去了平时多少的代码量。
对应NSURLSession的部分代码:
self.sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
3.创建请求任务
AF中创建任务的代码是这样的
__block NSURLSessionDataTask *dataTask = nil;
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
__block NSURLSessionUploadTask *uploadTask = nil;
url_session_manager_create_task_safely(^{
uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL];
});
__block NSURLSessionUploadTask *uploadTask = nil;
url_session_manager_create_task_safely(^{
uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData];
});
__block NSURLSessionUploadTask *uploadTask = nil;
url_session_manager_create_task_safely(^{
uploadTask = [self.session uploadTaskWithStreamedRequest:request];
});
__block NSURLSessionDownloadTask *downloadTask = nil;
url_session_manager_create_task_safely(^{
downloadTask = [self.session downloadTaskWithRequest:request];
});
它们都是在一个单列的串行队列中,同步创建的。这个线程的创建代理如下:(有删减)
//AFNetworking中所有的和创建任务相关的事件都放到了一个单例的串行队列中
static dispatch_queue_t url_session_manager_creation_queue() {
static dispatch_queue_t af_url_session_manager_creation_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
});
return af_url_session_manager_creation_queue;
}
static void url_session_manager_create_task_safely(dispatch_block_t block) {
dispatch_sync(url_session_manager_creation_queue(), block);
}
4.请求状态的跟踪
AF中协议的代码是这样的
//验证服务端证书链
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
// 创建默认的处理方式,PerformDefaultHandling方式将忽略credential这个参数
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else { // 如果没有实现自定义的验证过程
// 判断challenge的authenticationMethod
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 使用安全策略来验证
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
// 如果验证通过,根据serverTrust创建依据
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {// 有的话就返回UseCredential
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
//下载数据的获取
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
[self.mutableData appendData:data];
}
//把下载文件转移到我们之前设定的地址
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
NSError *fileManagerError = nil;
self.downloadFileURL = nil;
if (self.downloadTaskDidFinishDownloading) {
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (self.downloadFileURL) {
[[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError];
}
}
}
//数据解析 对请求任务结束的回调block进行线程优化
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
NSData *data = http://www.mamicode.com/nil;
if (self.mutableData) {
data = http://www.mamicode.com/[self.mutableData copy];
self.mutableData = http://www.mamicode.com/nil;
}
dispatch_async(url_session_manager_processing_queue(), ^{ //异步并行,加快数据的处理时间
//数据解析
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
//对请求结果的处理进行线程优化
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);
}
});
});
}
//上面GCD括号里的参数可能不好理解,两个参数都用了?:来设定,意思是AF的使用者定义了completionGroup就用使用者定义的,没定义就用AF提供的,第二个参数同理。
// 它们的类型dispatch_queue_t completionQueue; dispatch_group_t completionGroup,下面是这个 url_session_manager_completion_group()的代码:
static dispatch_group_t url_session_manager_completion_group() {
static dispatch_group_t af_url_session_manager_completion_group;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_session_manager_completion_group = dispatch_group_create();
});
return af_url_session_manager_completion_group;
}
//这个方法创建的队列是一个并行的队列,这加快了数据的处理速度
static dispatch_queue_t url_session_manager_processing_queue() {
static dispatch_queue_t af_url_session_manager_processing_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_session_manager_processing_queue = dispatch_queue_create("com.alamofire.networking.session.manager.processing", DISPATCH_QUEUE_CONCURRENT);
});
return af_url_session_manager_processing_queue;
}
5.请求结果处理
1.AFHTTPResponseSerializer
//这个方法根据在初始化方法中初始化的属性 `acceptableContentTypes` 和 `acceptableStatusCodes` 来判断当前响应是否有效。
- (BOOL)validateResponse:(NSHTTPURLResponse *)response data:(NSData *)data error:(NSError * __autoreleasing *)error
AFJSONResponseSerializer : AFHTTPResponseSerializer
- (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error
{
//1. 验证请求的有效性
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
//2. 解决一个空格引起的 [bug]
id responseObject = nil;
NSError *serializationError = nil;
BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
if (data.length > 0 && !isSpace) {
//3. 序列化 JSON
responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
} else {
return nil;
}
//4. 移除 JSON 中的 null
if (self.removesKeysWithNullValues && responseObject) {
responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return responseObject;
}
2.AFXMLParserResponseSerializer
responseObject =[[NSXMLParser alloc] initWithData:data];
3.AFXMLDocumentResponseSerializer
responseObject = [[NSXMLDocument alloc] initWithData:data options:self.options error:&serializationError];
4.AFPropertyListResponseSerializer
responseObject = [NSPropertyListSerialization propertyListWithData:data options:self.readOptions format:NULL error:&serializationError];
----------------------持续更新中 如有错误 恳请指正----------------------------
AFNetWorking VS NSURLSession