接下来我们来补充之前AFURLResponseSerialization这一块是如何解析数据的
@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>
/**
The response object decoded from the data associated with a specified response.
@param response The response to be processed.
@param data The response data to be decoded.
@param error The error that occurred while attempting to decode the response data.
@return The object decoded from the specified response data.
*/
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end
这实际是一个协议方法。而后面的解析类都是遵守这个协议方法,去做数据解析
AFHTTPResponseSerializer:
- (id)responseObjectForResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
return [[NSXMLParser alloc] initWithData:data];
}
方法调用了一个另外的方法之后,就把data返回来了
// 判断是不是可接受类型和可接受code,不是则填充error
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
// response是否合法标识
BOOL responseIsValid = YES;
// 验证的error
NSError *validationError = nil;
// 如果存在且是NSHTTPURLResponse
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
//主要判断自己能接受的数据类型和response的数据类型是否匹配,
//如果有接受数据类型,如果不匹配response,而且响应类型不为空,数据长度不为0
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
!([response MIMEType] == nil && [data length] == 0)) {
//进入If块说明解析数据肯定是失败的,这时候要把解析错误信息放到error里。
//如果数据长度大于0,而且有响应url
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;
}
//判断自己可接受的状态吗
//如果和response的状态码不匹配,则进入if块
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;
}
●简单来说,这个方法就是来判断返回数据与咱们使用的解析器是否匹配,需要解析的状态码是否匹配。如果错误,则填充错误信息,并且返回NO,否则返回YES,错误信息为nil。
●其中里面出现了两个属性值,一个acceptableContentTypes,一个acceptableStatusCodes,两者在初始化的时候有给默认值,我们也可以去自定义,但是如果给acceptableContentTypes定义了不匹配的类型,那么数据仍旧会解析错误。
●而AFHTTPResponseSerializer仅仅是调用验证方法,然后就返回了data。
AFJSONResponseSerializer:
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
//先判断是不是可接受类型和可接受code
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
//error为空,或者有错误,去函数里判断。
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
//返回空
return 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/1742
//如果数据为空
BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
//空则返回nil
if (data.length == 0 || isSpace) {
return nil;
}
NSError *serializationError = nil;
// 不为空解析Jason
id responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
if (!responseObject)
{
// 拿着json解析的error去填充错误信息
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return nil;
}
//判断是否需要移除Null值
if (self.removesKeysWithNullValues) {
return AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
//返回解析结果
return responseObject;
}
接下来我们接着看调用的第一个方法
// 判断是不是我们自己之前生成的错误信息,是的话返回YES
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
// 判断错误域名和传过来的域名是否一致,错误code是否一致
if ([error.domain isEqualToString:domain] && error.code == code) {
return YES;
}
//如果userInfo的NSUnderlyingErrorKey有值,则在判断一次。
else if (error.userInfo[NSUnderlyingErrorKey]) {
return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
}
return NO;
}
这里可以注意,我们这里传过去的code和domain两个参数分别为NSURLErrorCannotDecodeContentData、AFURLResponseSerializationErrorDomain,这两个参数是我们之前判断response可接受类型和code时候自己去生成错误的时候填写的。
调用第二个方法
static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
//分数组和字典
if ([JSONObject isKindOfClass:[NSArray class]]) {
//生成一个数组
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
for (id value in (NSArray *)JSONObject) {
//调用自己
[mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
}
//看我们解析类型是mutable还是非muatable,返回mutableArray或者array
return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
} else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
id value = (NSDictionary *)JSONObject[key];
//value空则移除
if (!value || [value isEqual:[NSNull null]]) {
[mutableDictionary removeObjectForKey:key];
} else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
//如果数组还是去调用自己
mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
}
}
return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
}
return JSONObject;
}
方法主要还是通过递归的形式实现。比较简单。
第三个
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
if (!error) {
return underlyingError;
}
if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
return error;
}
NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;
return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}
方法主要是把json解析的错误,赋值给我们需要返回给用户的error上。比较简单
至此,AFJSONResponseSerializer就讲完了
现在我们回到一开始初始化的这行代码上:
self.operationQueue.maxConcurrentOperationCount = 1;
首先我们要明确一个概念,这里的并发数仅仅是回调代理的线程并发数。而不是请求网络的线程并发数。请求网络是由NSURLSession来做的,它内部维护了一个线程池,用来做网络请求。它调度线程,基于底层的CFSocket去发送请求和接收数据。这些线程是并发的。
明确了这个概念之后,我们来梳理一下AF的整个流程和线程的关系:
● 一开始初始化sessionManager的时候,一般都是在主线程。
● 然后我们调用get
或者post
等去请求数据,接着会进行request
拼接,AF代理的字典映射,progress
的KVO
添加等等,到NSUrlSession
的resume
之前这些准备工作,仍旧是在主线程中的。
● 然后我们调用NSUrlSession
的resume
,接着就跑到NSUrlSession
内部去对网络进行数据请求了,在它内部是多线程并发的去请求数据的。
● 紧接着数据请求完成后,回调回来在我们一开始生成的并发数为1的NSOperationQueue
中,这个时候会是多线程串行的回调回来的。
● 然后我们到返回数据解析那一块,我们自己又创建了并发的多线程,去对这些数据进行了各种类型的解析。
●最后我们如果有自定义的completionQueue
,则在自定义的queue
中回调回来,也就是分线程回调回来,否则就是主队列,主线程中回调结束。
最后我们来解释解释为什么回调Queue要设置并发数为1:
我认为AF这么做有以下两点原因:
1.众所周知,AF2.x所有的回调是在一条线程,这条线程是AF的常驻线程,而这一条线程正是AF调度request的思想精髓所在,所以第一个目的就是为了和之前版本保持一致。
2.因为跟代理相关的一些操作AF都使用了NSLock。所以就算Queue的并发数设置为n,因为多线程回调,锁的等待,导致所提升的程序速度也并不明显。反而多task回调导致的多线程并发,平白浪费了部分性能。
而设置Queue的并发数为1,(注:这里虽然回调Queue的并发数为1,仍然会有不止一条线程,但是因为是串行回调,所以同一时间,只会有一条线程在操作AFUrlSessionManager的那些方法。)至少回调的事件,是不需要多线程并发的。回调没有了NSLock的等待时间,所以对时间并没有多大的影响。(注:但是还是会有多线程的操作的,因为设置刚开始调起请求的时候,是在主线程的,而回调则是串行分线程。)
参考资料:
AFNetworking到底做了什么
结合最新的AF源码做了适当的修改
文章所用到的注释后的源码