通读AFN①--从创建manager到数据解析完毕

流程梳理

今天开始会写几篇关于AFN源码解读的一些Blog,首先要梳理一下AFN的整体结构(主要是讨论2.x版本的Session访问模块):
我们先看看我们最常用的一段代码:

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:@"https://www.baidu.com" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {// ... successHandler
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {// ... failureHandler
}];

在前面关于 AFN URLEncode 的文章说道,AFN将网络访问分为三个过程化的模块,下面我把第一部分再分为两个步骤:

1.访问前的准备:使用AFURLRequestSerialization类创建一个新的URLRequest对象(用于即将进行的网络访问),对传递过来的URLrequest对象进行三步加工:
①配置默认网络配置,如(allowsCellularAccess,cachePolicy,HTTPShouldHandleCookies,HTTPShouldUsePipelining,networkServiceType,timeoutInterval)
②将request的HTTPHeader赋给新的request
③将parameter字典转为queryString,拼接在URLRequest的URL后面.如果是POST,PUT,PATCH方法,则放在HTTPBody中,并设置Content-Type头为表单类型:application/x-www-form-urlencoded

2.用1中所得的mutableRequest对象创建dataTask

3.访问过程中,将代理职责下放给AFURLSessionManagerTaskDelegate,通过代理方法接收数据。

4.完全接受到数据或失败之后的处理:失败回调、成功后解析然后回调。

上面四个步骤都是在[manager GET: parameters: success: failure:]这个方法中完成的,而在进行网络访问之前的[AFHTTPSessionManager manager]是对网络访问过程组件的初始化,也就是,在AFHTTPSessionManager+manager方法中,完成了对自己和requestSerializer以及responseSerializer的初始化工作,+manager方法内部的代码:

self.baseURL = url;self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];

可以看出requestSerializer和responseSerializer对象都是按照默认的构造方法serializer创建的,同时可以看出responseSerializer默认使用了JSON的解析方式,着也是为什么当使用AFN进行网络请求时,JSON会自动进行解析的原因。看到这里我们也了解了如果想进行修改默认的request和response序列化方式修改,在何时添加这部分代码。就是在manager的默认设置完成之后,在开始进行网络访问三步走之前:

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFXMLParserResponseSerializer serializer];
[manager GET:@"https://www.baidu.com" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {// ... successHandler
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {// ... failureHandler
}];

我们能改变的不仅仅是request和reponse按照什么格式序列化,还可以改变默认的session配置,进行创建Task的session对象在AFN中成为了AFHTTPSessionManager的属性,如果不使用构造方法- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration 传给它一个值,它会在AFHTTPSessionManager的父类AFURLRequestSerialization中默认配置的,不光如此,而且还配置了AFHTTPSessionManager的很多重要属性,在AFURLRequestSerialization-initWithBaseURL: sessionConfiguration:中:

if (!configuration) {configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}self.sessionConfiguration = configuration;self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];self.responseSerializer = [AFJSONResponseSerializer serializer];self.securityPolicy = [AFSecurityPolicy defaultPolicy];self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {for (NSURLSessionDataTask *task in dataTasks) {[self addDelegateForDataTask:task 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];}
}];[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:nil];return self;

这些默认的配置大多是不可以在外部修改,因为大都为readonly属性,只是在实现文件中给了修改的接口。例如:

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
// 设置最大并发操作数
manager.operationQueue.maxConcurrentOperationCount = 3; // error
[manager GET: parameter: success: failure:];

AFN网络访问的默认设置大多都是不希望用户修改的,对外提供的接口也仅仅局限于request和response的序列化方式的修改。
看完了这些,我们来着重看一下网络访问三步走的过程:

1.访问前的准备:使用AFURLRequestSerialization类创建一个新的URLRequest对象

这一部分的很多知识点,在这篇文章 iOS. PercentEscape是错用的URLEncode,看看AFN和Facebook吧中有介绍,这里说一些补充的内容:
首先是requestSerializer的创建细节:这个虽然不属于这部分内容(它是在+manager方法中就创建了),但有些问题还需注意:

这个创建过程主要是设置默认编码为UTF8,对Accept-Language、User-Agent两个头的初始化,设置允许queryString放在URL中的HTTP请求方法为@"GET", @"HEAD", @"DELETE"、添加对@[@"allowsCellularAccess", @"cachePolicy", @"HTTPShouldHandleCookies", @"HTTPShouldUsePipelining", @"networkServiceType", @"timeoutInterval"]属性值(这些key通过一个静态数组获得)的观察者为本身。

需要注意的是请求头本来是Request的属性,这里设置请求头是用requestSerilizer对象的一个字典属性mutableHTTPRequestHeaders将它们先存储起来,以备在修改传递过来的request对象过程中使用。

为什么要KVO以上6个属性?

字典属性mutableObservedChangedKeyPaths用来存储这6个属性值中非空的值,如果这6个属性中的任何一个被赋了新值,就会在observeValueForKeyPath:中检查新值是否为空,如果为空,就从mutableObservedChangedKeyPaths中移出这个对象,表示不再需要考虑这个值对配置的影响。
而这些非空的值会在进行网络访问前创建新的mutableRequest对象的时候一一赋给它(这些属性本来就是URLRequest对象的属性)。

这个过程我们可以换一个思路实现,就是非空给属性赋值,空时赋给属性NSNull,在将这些属性赋给mutableRequest的时候判断是否为NSNull,如果是,就不赋值了。相比之下AFN的做法对扩展性更好一些。而这种方法的使用在AFN是非常常见的。

下面我们就看一下mutableRequest创建的细节吧:

- (NSMutableURLRequest *)requestWithMethod:(NSString *)methodURLString:(NSString *)URLStringparameters:(id)parameterserror:(NSError *__autoreleasing *)error
{NSParameterAssert(method);NSParameterAssert(URLString);NSURL *url = [NSURL URLWithString:URLString];NSParameterAssert(url);NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];mutableRequest.HTTPMethod = method;// 给mutableRequest赋值刚才在AFHTTPRequestSerializerObservedKeyPaths存储的属性,已经去掉了空值。for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];}}// 将HTTPRequestHeaders字典属性中的Header传给mutableRequest, 将格式化好的queryString传给mutableRequestmutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];return mutableRequest;
}

刚才的大费口舌就是刚好是对这段代码的解释。
准备好了request,我们就来看一下如何使用request创建dataTask

2.使用准备好的mutableRequest对象创建dataTask

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod: URLString: parameters: failure:方法中的的后半段:

__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { // 下面会解读这一句if (error) {if (failure) {failure(dataTask, error);}} else {if (success) {success(dataTask, responseObject);}}
}];return dataTask;

其中的failure和success实际上是由我们使用者传递过来,这段非常简单的代码同样是有点机关的,这其中包含了AFN设计中使用的将代理职责转移的思想,尽管我们平常也使用过类似的代码,但还是研读一下AFN如何实现的吧:

上面的dataTask的创建的核心代码实现是这样的:

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)requestcompletionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{__block NSURLSessionDataTask *dataTask = nil;dispatch_sync(url_session_manager_creation_queue(), ^{dataTask = [self.session dataTaskWithRequest:request];});[self addDelegateForDataTask:dataTask completionHandler:completionHandler]; // 下面有解析return dataTask;
}

如上,AFN会选择在它自定义的串行队列url_session_manager_creation_queue(这个队列标记了label:"com.alamofire.networking.session.manager.creation")中采用同步的方式创建dataTask。
在dataTask被创建之后将代理职责下方给了AFURLSessionManagerTaskDelegate对象,我们可以通过查看[self addDelegateForDataTask:dataTask completionHandler:completionHandler];得出:

- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTaskcompletionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];delegate.manager = self; // AFURLSessionManagerTaskDelegate弱引用它的管理者(AFHTTPSessionManager对象)delegate.completionHandler = completionHandler; // 将完成的回调(failure和success的处理)传递给AFURLSessionManagerTaskDelegatedataTask.taskDescription = self.taskDescriptionForSessionTasks;[self setDelegate:delegate forTask:dataTask];
}

而在的实现中:

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegateforTask:(NSURLSessionTask *)task
{NSParameterAssert(task);NSParameterAssert(delegate);[self.lock lock];self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;[self.lock unlock];
}

这里AFNHTTPSessionManager将单个dataTask的代理职责下放给了一个AFURLSessionManagerTaskDelegate对象,但是这个对象仍然受manager的控制,manager会用一个可变字典类型的属性mutableTaskDelegatesKeyedByTaskIdentifier存储它管理的所有的dataTask和这个dataTask对应的AFURLSessionManagerTaskDelegate对象的关系,而具体的任务下放就是通过这种关系来实现的。

下面就边介绍数据请求与接收的过程阶段边解释如何通过这种关系将代理职责下放。

3.网络访问过程中

这一过程是由dataTask的resume方法开始的。AFHTTPSessionManager的成员session会使用上面的request进行网络请求,当接收到数据之后进入回调,AFN已将session在AFHTTPSessionManager的父类AFURLSessionManager中默认设置了self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];并且session的代理方法也已在AFURLSessionManager类中实现。而AFN在这个实现的过程中将每次接收到的数据都交给了当前dataTask对应的AFURLSessionManagerTaskDelegate对象处理,在这里实现了职责下放:
在AFURLSessionManager.m中:

- (void)URLSession:(NSURLSession *)sessiondataTask:(NSURLSessionDataTask *)dataTaskdidReceiveData:(NSData *)data
{AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask]; // 找到dataTask对应的AFURLSessionManagerTaskDelegate对象[delegate URLSession:session dataTask:dataTask didReceiveData:data]; // 代理职责下放if (self.dataTaskDidReceiveData) {self.dataTaskDidReceiveData(session, dataTask, data);}
}// 如何找到dataTask对应的AFURLSessionManagerTaskDelegate对象
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {NSParameterAssert(task);AFURLSessionManagerTaskDelegate *delegate = nil;[self.lock lock];delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)]; // 根据taskId,之前以key为taskId、value为AFURLSessionManagerTaskDelegate对象的形式存入字典中。[self.lock unlock];return delegate;
}

而真正处理网络请求的类是AFURLSessionManagerTaskDelegate,它从未被设置为session的delegate,而是在AFHTTPSessionManager(AFURLSessionManager)对session的代理方法的实现中主动调用。

这个数据最后被这样处理,在AFURLSessionManagerTaskDelegate中

- (void)URLSession:(__unused NSURLSession *)sessiondataTask:(__unused NSURLSessionDataTask *)dataTaskdidReceiveData:(NSData *)data
{[self.mutableData appendData:data];
}

我们可以看到AFURLSessionManagerTaskDelegate类有一个mutableData属性用来拼接接收的数据。
看一下接收完毕之后是如何处理的,先是在AFURLSessionManager中:

- (void)URLSession:(NSURLSession *)sessiontask:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];// delegate may be nil when completing a task in the backgroundif (delegate) {[delegate URLSession:session task:task didCompleteWithError:error];[self removeDelegateForTask:task];}if (self.taskDidComplete) {self.taskDidComplete(session, task, error);}
}

这里先找到task对应的AFURLSessionManagerTaskDelegate对象,同样是通过dataTask的Id,然后将处理任务交给这个delegate对象,等它处理之后,sessionManager会将这个delegate对象从字典中移除:

- (void)removeDelegateForTask:(NSURLSessionTask *)task {NSParameterAssert(task);AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];[self.lock lock];[delegate cleanUpProgressForTask:task];[self removeNotificationObserverForTask:task];[self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];[self.lock unlock];
}

这样manager管理的session进行的一次dataTask就完毕了。

再看一下在AFURLSessionManagerTaskDelegate中,如何具体处理的

- (void)URLSession:(__unused NSURLSession *)sessiontask:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"__strong AFURLSessionManager *manager = self.manager;__block id responseObject = nil;__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;//Performance Improvement from #2672NSData *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;}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(), ^{[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];});});});}
#pragma clang diagnostic pop
}

取出SessionManager,和sessionManager的responseSerializer属性,创建userInfo字典,存放数据解析的组件对象和返回的数据等

如果有错误:
1.userInfo存入error,key为完成错误的标记,
2.创建队列任务:在主队列中完成回调(由最开始传入的success和failure处理)、然后向主线程发送附带userInfo的任务完成的通知,
3.将2创建的任务放在静态的队列组url_session_manager_completion_group()中执行。

没有错误:
在异步的静态队列url_session_manager_processing_queue(label是"com.alamofire.networking.session.manager.processing")中处理:
1.用manager的responseSerializer属性进行数据解析,将data解析为responseObject
1.1.解析正确,将responseObject存入userInfo中,
1.2.解析失败,将错误信息serializationError存入userInfo,
2.创建队列任务:在主队列中完成回调(由最开始传入的success和failure处理)、然后向主线程发送附带userInfo的任务完成的通知,
3.将2创建的任务放在静态的队列组url_session_manager_completion_group()中执行。

要说明的一点是:AFN只负责发送通知,而没有对通知进行接收的处理,这部分需要使用者自己完成。
现在就只剩下数据解析的过程了还没有介绍了。

4.数据解析

这里主要体现的是面向对象多态的特性。
在无论我们使用AFHTTPSessionManager对象或是使用AFURLSessionManager对象创建的dataTask在数据解析阶段,都会调用上面刚刚分析完的代码中的responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];这一句进行数据解析,而在HTTPSessionManager的manager方法中默认为我们创建了JSON类型的解析器self.responseSerializer = [AFJSONResponseSerializer serializer];,这样在执行过程中,就会动态地调用AFJSONResponseSerializer的-responseObjectForResponse: data: error:方法,它的实现是这样的:

- (id)responseObjectForResponse:(NSURLResponse *)responsedata:(NSData *)dataerror:(NSError *__autoreleasing *)error
{if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {return nil;}}id responseObject = nil;NSError *serializationError = nil;// Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.// See https://github.com/rails/rails/issues/1742BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];if (data.length > 0 && !isSpace) {responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];} else {return nil;}if (self.removesKeysWithNullValues && responseObject) {responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);}if (error) {*error = AFErrorWithUnderlyingError(serializationError, *error);}return responseObject;
}

这是一个非常简单的算法,
1.先调用了从父类(AFHTTPResponseSerializer)集成而来的数据验证方法,如果验证失败了,并且确认错误是由AFN解析引起的,返回nil,
2.检验data是否为空或者一个空格这样的无效数据,失败返回nil,否则将data解析为JSONObject
3.如果removesKeysWithNullValues属性设置为YES,那么要去掉2中的JSONObject中的value等于[NSNull null]的元素。

AFJSONResponseSerializer类是AFHTTPResponseSerializer的子类,一些初始化的设置,还有验证数据的方法都是在AFJSONResponseSerializer中完成的。

看一下AFJSONResponseSerializer类:

- (instancetype)init {// ...self.stringEncoding = NSUTF8StringEncoding;self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)]; // 只接受statusCode为2xxself.acceptableContentTypes = nil; // 接收的Content-Type,需要子类的init中重写return self;
}- (BOOL)validateResponse:(NSHTTPURLResponse *)responsedata:(NSData *)dataerror:(NSError * __autoreleasing *)error
{BOOL responseIsValid = YES;NSError *validationError = nil;if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]]) {if ([data length] > 0 && [response URL]) {NSMutableDictionary *mutableUserInfo = [@{NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],NSURLErrorFailingURLErrorKey:[response URL],AFNetworkingOperationFailingURLResponseErrorKey: response,} mutableCopy];if (data) {mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;}validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);}responseIsValid = NO;}if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {NSMutableDictionary *mutableUserInfo = [@{NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],NSURLErrorFailingURLErrorKey:[response URL],AFNetworkingOperationFailingURLResponseErrorKey: response,} mutableCopy];if (data) {mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;}validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);responseIsValid = NO;}}if (error && !responseIsValid) {*error = validationError;}return responseIsValid;
}

init不再多说,主要是验证方法- (BOOL)validateResponse: data: error:,在这个方法内部完成了这些工作:
1.设置验证通过responseIsValid的默认值YES,错误validationError为nil
2.验证
 2.1对response的MIME类型验证:如果acceptableContentTypes属性中不包含response的MIME类型,则认为验证失败,responseIsValid设为NO,本地化错误描述,并将描述、response的URL、response对象存入userInfo字典,用这个userInfo字典创建Domain为AFURLResponseSerializationErrorDomain的NSError对象
 2.2对response.statusCode验证:如果acceptableStatusCodes属性中不包含response.statusCode,则认为失败,处理同2.1,
3.将错误赋给参数error,返回responseIsValid。


对于其他类型的解析与JSON类似,这里列举一下经过解析后的的id responseObject对应的类型:

manager的responseSerializer属性类型解析后的responseObject类型
AFHTTPResponseSerializerNSData
AFJSONResponseSerializerJSONObject(NSDictionary或NSArray)
AFXMLParserResponseSerializerNSXMLParser
AFXMLDocumentResponseSerializerNSXMLDocument
AFPropertyListResponseSerializerpropertyList(NSDictionary或NSArray)
AFImageResponseSerializeriOS、TV、Watch:UIImage    Mac:NSImage
AFCompoundResponseSerializer用responseSerializers数组中对象依次解析,
第一个失败,则用第二个解析,依次类推,返回第一个成功的结果

当获取responseObject对象后,直接按类型使用即可,例如如果设置了manager.responseSerializer = [AFXMLParserResponseSerializer serializer],就要这样解析:

success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {NSXMLParser *saxParser = (NSXMLParser *)responseObject;saxParser.delegate = self;[saxParser parse];
}

不过若要使用相同的manager对象进行下一次网络访问,如果不知道response的Content-Type,就要将manager的responseSerializer复原,重新设置为:

manager.responseSerializer = [AFJSONRequestSerializer serializer]; // 如果manager为AFHTTPSessionManager
manager.responseSerializer = [AFHTTPRequestSerializer serializer]; // 如果manager为AFURLSessionManager

转载于:https://www.cnblogs.com/Mike-zh/p/5167017.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/289265.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学前教育试题库及答案_最新《学前教育学》专科-试题库及答案资料

精品文档精品文档学前教育学专科题库及答案一、单项选择题:(从下列各题备选答案中选出最适合的一个答案。共120题,每题1分)1——5DACBB 6——10DACDB 11——15ABDAC 16——20 BCDDC 21——25BBBCC26——30DCCAD 31——35DBBAB 36——40ACA…

python画4维图_用Python 画个六维图,涨姿势了

关注后你就是我的人了 我们的大脑通常最多能感知三维空间,超过三维就很难想象了。尽管是三维,理解起来也很费劲,所以大多数情况下都使用二维平面。来自维基百科 不过,我们仍然可以绘制出多维空间,今天就来用 Python 的…

[Windows]_[0基础]_[Release程序的崩溃报告minidump解决方式]

场景:1. Release的程序崩溃时,崩溃报告能够让开发者查明代码哪里出了问题,用处大大的。2. 仅仅实用VS的编译器才支持,所以MinGW就无缘了。3. 使用了未处理异常过滤处理函数.4. 生成的.dmp文件用zlib库压缩, 用到下面的ZipHelper类&#xff0c…

高考地理背熟这些知识可以拿80%的分数(1)

一、地理特征的描述 (一).区域自然地理特征的描述: 1、分析区域自然地理特征: 从地形、气候、水文、植被、河流、土壤、自然资源、自然灾害、自然(生态)环境等几方面入手。

CF Theatre Square

Theatre Square time limit per test2 secondsmemory limit per test64 megabytesinputstandard inputoutputstandard outputTheatre Square in the capital city of Berland has a rectangular shape with the size n  m meters. On the occasion of the citys anniversary,…

迅捷路由器 服务器无响应,如果路由器重启还是上不了网 几招搞定

如果网速很慢重启了路由器之后结果还是慢,而且甚至上不了网了那该怎么办。如果有这种情况原因其实有很多很多的可能,需要逐个排查,首先需要进入192.168.1.1路由器的管理设置界面,查看路由器的运行状态。路由器设置、路由器没有成功…

Azure DevOps 中 Dapr项目自动部署流程实践

注:本文中主要讨论 .NET6.0项目在 k8s 中运行的 Dapr 的持续集成流程, 但实际上不是Dapr的项目部署到K8s也是相同流程,只是k8s的yaml配置文件有所不同流程选择基于 Dapr 的项目持续集成包含以下流程编译并打包项目构建 Dockerfile,并推送镜像push image至…

matlab求kcf算法响应图_Kernelized Correlation Filters(KCF)算法

目前在online visual tracking这个领域,已经涌现出很多的跟踪算法,比较知名如TLD,Struck,OAB,CT等等。但是能做到非常快速而且效果还不错的相对就较少了,好多算法都是刚刚能实时,而且还是在图像…

React Native之js同步调用安卓原生方法@ReactMethod(isBlockingSynchronousMethod = true)

1 问题 之前的代码js调用安卓原生都是用的异步方法,比如callback, promiss,异步的话,我们一般是在安卓原生有耗时操作,才用异步,如果我要离开返回,就需要js调用安卓同步方法 利用callback实现js调用原生可以参考我的这篇博客 React Native实现js调用安卓原生代码 React Nat…

用POP动画引擎实现弹簧动画(POPSpringAnimation)

效果图: #import "ViewController.h" #import <POP.h>interface ViewController ()property (nonatomic, weak) UIView *testView;endimplementation ViewController- (void)viewDidLoad {[super viewDidLoad];self.view.backgroundColor [UIColor blackColor…

地理知识归纳:影响降水的九大因素

降水指大气中水汽凝结降落的过程,包括降雨、下雪、冰雹等形式,降水的多少要受很多因素的影响,但主要条件是三个:充足的水汽供应,气流上升达到过饱和状态,足够的凝结核。通常情况下,我们不需要考虑凝结核的问题,只是考虑有没有充足的水汽和促使气流上升的机制就可以,归…

linux 查看cpu_作为高级Java,你应该了解的Linux知识

作为一个javaer&#xff0c;我以前写过很多关于Linux的文章。但经过多年的观察&#xff0c;发现其实对于大部分人&#xff0c;有些东西压根就用不着。用的最多的&#xff0c;就是到线上排查个问题而已&#xff0c;这让人很是苦恼。那么&#xff0c;我们就将范围再缩小一下。最有…

uva-10305-水题-拓扑排序

uva-10305-水题-拓扑排序 输入n,m,n代表点数,m代表边数(i,j),排序时i在j前面,没出现的点随意排 #include <iostream> #include<stdio.h> #include<math.h> #include<memory.h> using namespace std;const int maxNum 120; int a, b; int map[maxNum][…

layer和3D仿射变换

1、视图的显示基于图层&#xff0c;通过控制图层同样能控制显示效果&#xff0c;获取当前的视图的layer,并为其增加圆角边框。 //设置layer边框的宽度为2view.layer.borderWidth2;//如果需要为layer添加颜色需要转换为CGColor对象view.layer.borderColor[UIColor greenColor].C…

服务器维修质保合同,服务器维护保修合同(标准版本).pdf

服务器维护保修合同甲 方 &#xff1a;乙 方 &#xff1a;。服务器维护保修合同甲 方&#xff1a;住 所&#xff1a;法定代表人&#xff1a;联 系 电 话 &#xff1a; 传 真&#xff1a;联 系 地 址 &#xff1a;邮 政 编 码 &#xff1a;乙 方&#xff1a;住 所&#xff1a;法…

React Native之Android原生通过DeviceEventEmitter发送消息给js

1 问题 Android原生向js发消息,并且可以携带数据 2 实现原理 Android原生可以使用RCTEventEmitter来注册事件,然后这里需要指定事件的名字,然后在js那端进行监听同样事件的名字监听,就可以收到消息得到数据 Android注册关键代码 reactContext.getJSModule(DeviceEventManag…

knex 单表查询_knex.js

软件简介knex.js 是一个查询构建器&#xff0c;用于 PostgreSQL, MySQL 和 SQLite3。它设计灵活&#xff0c;轻便和有趣。特性&#xff1a;例子&#xff1a;var knex require(knex)({dialect: sqlite3,connection: {filename: ./data.db}});// Create a tableknex.schema.crea…

IOS-网络(大文件下载)

一、不合理方式 1 //2 // ViewController.m3 // IOS_0131_大文件下载4 //5 // Created by ma c on 16/1/31.6 // Copyright © 2016年 博文科技. All rights reserved.7 //8 9 #import "ViewController.h" 10 11 interface ViewController ()<NSURLConne…

地理素养的核心构成和主要特点

素养教育已成为21世纪国际教育发展的重大课题和紧迫任务。新一轮地理课程改革把地理素养置于地理课程目标的核心地位。因此,统一认识和准确把握地理素养的内涵与特质,对于促进学生的全面发展具有十分重要的意义。 一、地理素养的内涵与组成 地理素养是指学习者经过地理学习后…

【开题报告】基于SpringBoot的电子二手产品交易平台的设计与实现

1.研究背景 随着互联网的快速发展和普及&#xff0c;电子商务行业蓬勃发展&#xff0c;二手产品交易作为电子商务领域的一个重要分支也得到了广泛关注。传统的线下二手交易存在一些问题&#xff0c;例如信息不对称、交易风险高、交易流程繁琐等&#xff0c;这些问题限制了用户…