这次继续介绍文件夹Serialization下的类AFURLResponseSerialization。这次介绍就不拆分了,整体来看一下.h和.m文件。
协议AFURLResponseSerialization通过一个解码数据转换成一个更有用的对象表示的对象被遵守,根据服务器响应的细节。响应序列化器可能另外执行在传入的响应和数据上的验证。
比如,一个JSON响应序列器可以检查一个可接受的状态码(“2 xx”范围)和内容类型(application / JSON),解码一个有效的JSON响应成为一个对象。
AFURLResponseSerialization
是用来将返回的response处理成相应的格式。
在这个协议里协议方法的作用是响应对象解码关联到一个指定响应的数据。意思就是对特定response的data进行解码。具体方法如下
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
|
AFHTTPResponseSerializer
可以通过+ serializer
和- init
方法进行初始化,实际上+ serializer
内只是调用了- init
+ (instancetype)serializer {
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
// 设置字符串编码类型,可接受的状态码,可接受的MIME类型
self.stringEncoding = NSUTF8StringEncoding;
self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
self.acceptableContentTypes = nil;
return self;
}
|
acceptableStatusCodes和acceptableContentTypes可以通过外部进行设置
@property (nonatomic, copy, nullable) NSIndexSet *acceptableStatusCodes;
@property (nonatomic, copy, nullable) NSSet <NSString *> *acceptableContentTypes;
|
然后可以调用- [validateResponse:data:error:]
检查这个response是否包含可接受的状态码和可接受MIME类型来验证response的有效性,子类也可以增加特定域名检查,- [responseObjectForResponse:data:error]
也是调用了这个方法,返回data
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
// 调用- [validateResponse:data:error:]方法,返回data
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
return data;
}
|
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
// 设置初始值
BOOL responseIsValid = YES;
NSError *validationError = nil;
// 检查这个response是否包含可接受的状态码和可接受MIME类型
if (error && !responseIsValid) {
*error = validationError;
}
// 返回response是否有效性
return responseIsValid;
}
|
检查这个response是否包含可接受的状态码和可接受MIME类型
// 检查response是否为空,以及response是否是NSHTTPURLResponse类
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
// acceptableContentTypes不为空并且response的MIME类型不在可接受的范围里
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;
}
// acceptableStatusCodes不为空并且acceptableStatusCodes包含response的状态码,response的URL也存在
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;
}
}
|
但是这里有个疑问,假如response为nil或者response不是NSHTTPURLResponse
类,那下面的操作均不会对responseIsValid布尔值进行修改,最后返回的是个YES,但是这样的response不应该是NO么?
AFJSONResponseSerializer
是继承于AFHTTPResponseSerializer
外部可以设置NSJSONReadingOptions
和是否移除空值的key
@property (nonatomic, assign) NSJSONReadingOptions readingOptions;
@property (nonatomic, assign) BOOL removesKeysWithNullValues;
|
转换object的时候,会检查data是否是空格,这个是Safari的一个bug,具体请看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.
- (id)responseObjectForResponse:(NSURLResponse *)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;
}
}
id responseObject = nil;
NSError *serializationError = nil;
// 判断是否是空格
BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
if (data.length > 0 && !isSpace) {
responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
} else {
return nil;
}
// 调用AFJSONObjectByRemovingKeysWithNullValues把空值的key都移除掉,返回object
if (self.removesKeysWithNullValues && responseObject) {
responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return responseObject;
}
|
AFXMLParserResponseSerializer
则是直接校验response后,用data初始化NSXMLParser对象并返回
- (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];
}
|
AFPropertyListResponseSerializer
也是类似的处理
- (id)responseObjectForResponse:(NSURLResponse *)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;
}
}
id responseObject;
NSError *serializationError = nil;
if (data) {
responseObject = [NSPropertyListSerialization propertyListWithData:data options:self.readOptions format:NULL error:&serializationError];
}
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return responseObject;
}
|
AFImageResponseSerializer
在验证response之后,会根据设置是否自动解压automaticallyInflatesResponseImage布尔值,来对imageData按图片比例返回UIImage对象
@property (nonatomic, assign) CGFloat imageScale;
@property (nonatomic, assign) BOOL automaticallyInflatesResponseImage;
|
- (id)responseObjectForResponse:(NSURLResponse *)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;
}
}
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
// iOS需要手动解压图片
if (self.automaticallyInflatesResponseImage) {
return AFInflatedImageFromResponseWithDataAtScale((NSHTTPURLResponse *)response, data, self.imageScale);
} else {
return AFImageWithDataAtScale(data, self.imageScale);
}
#else
// MacOS可以直接使用NSBitmapImageRep来解压
NSBitmapImageRep *bitimage = [[NSBitmapImageRep alloc] initWithData:data];
NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize([bitimage pixelsWide], [bitimage pixelsHigh])];
[image addRepresentation:bitimage];
return image;
#endif
return nil;
}
|
如果不解压的话,就直接根据imageData和scale来创建Image,但是这有个疑问是,AF为什么要创建两次image,我觉得可以直接使用- [imageWithData:scale:]方法
static UIImage * AFImageWithDataAtScale(NSData *data, CGFloat scale) {
UIImage *image = [UIImage af_safeImageWithData:data];
if (image.images) {
return image;
}
return [[UIImage alloc] initWithCGImage:[image CGImage] scale:scale orientation:image.imageOrientation];
}
|
但是如果用imageWithData转成UIImage对象后,由于网络图片PNG和JPG都是压缩格式,需要解压成bitmap后才能渲染到屏幕,这时会在主线程对图片进行解压操作,这是比较耗时的,可能还会对主线程造成阻塞,所以AF还提供了AFInflatedImageFromResponseWithDataAtScale
方法,对PNG和JPG解压后,返回UIImage对象,这样避免了在主线程的解压操作,不会对主线程造成卡顿
static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale) {
if (!data || [data length] == 0) {
return nil;
}
// 创建CGImageRef
CGImageRef imageRef = NULL;
// 用data创建CGDataProviderRef
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
if ([response.MIMEType isEqualToString:@"image/png"]) {
imageRef = CGImageCreateWithPNGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
} else if ([response.MIMEType isEqualToString:@"image/jpeg"]) {
imageRef = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
if (imageRef) {
CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(imageRef);
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(imageColorSpace);
// 如果色彩空间是CMKY,CGImageCreateWithJPEGDataProvider是不会进行处理的,也就是不进行解压,将调用AFImageWithDataAtScale返回image
if (imageColorSpaceModel == kCGColorSpaceModelCMYK) {
CGImageRelease(imageRef);
imageRef = NULL;
}
}
}
CGDataProviderRelease(dataProvider);
// 不符合解压条件的,将调用AFImageWithDataAtScale返回image,但是这里如果符合解压条件的也会调用,以及下面会对超出大小的,直接返回image,这里我觉得应该统一对不符合条件的返回image,符合条件的就不需要调用AFImageWithDataAtScale
UIImage *image = AFImageWithDataAtScale(data, scale);
if (!imageRef) {
if (image.images || !image) {
return image;
}
// 这里调用CGImageCreateCopy,只会对图形本身结构进行拷贝,底层的数据是不会拷贝的
imageRef = CGImageCreateCopy([image CGImage]);
if (!imageRef) {
return nil;
}
}
// 设置图片的宽和高和存储一个像素所需要用到的字节
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
// 如果图片大小宽高乘积超过1024*1024或者bitsPerComponent大于8都不解压了,因为bitmap是一直存在UIImage对象里的,可能会把内存爆了
if (width * height > 1024 * 1024 || bitsPerComponent > 8) {
CGImageRelease(imageRef);
return image;
}
// 画布参数
size_t bytesPerRow = 0;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
if (colorSpaceModel == kCGColorSpaceModelRGB) {
uint32_t alpha = (bitmapInfo & kCGBitmapAlphaInfoMask);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wassign-enum"
if (alpha == kCGImageAlphaNone) {
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaNoneSkipFirst;
} else if (!(alpha == kCGImageAlphaNoneSkipFirst || alpha == kCGImageAlphaNoneSkipLast)) {
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaPremultipliedFirst;
}
#pragma clang diagnostic pop
}
// 创建画布
CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
CGColorSpaceRelease(colorSpace);
if (!context) {
CGImageRelease(imageRef);
return image;
}
// 在画布上画出图片
CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, width, height), imageRef);
// 保存成CGImageRef
CGImageRef inflatedImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
// 再转成UIImage对象
UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation];
CGImageRelease(inflatedImageRef);
CGImageRelease(imageRef);
return inflatedImage;
}
|
AFCompoundResponseSerializer
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
// 遍历responseSerializers
for (id <AFURLResponseSerialization> serializer in self.responseSerializers) {
// 如果serializer不是AFHTTPResponseSerializer类,则继续
if (![serializer isKindOfClass:[AFHTTPResponseSerializer class]]) {
continue;
}
NSError *serializerError = nil;
// 一层一层的调用自己的- [responseObjectForResponse:data:error:],直到返回responseObject
id responseObject = [serializer responseObjectForResponse:response data:data error:&serializerError];
if (responseObject) {
if (error) {
*error = AFErrorWithUnderlyingError(serializerError, *error);
}
return responseObject;
}
}
return [super responseObjectForResponse:response data:data error:error];
}
|
- AFNetworking (3.1.0) 源码解析 <三>
今天要介绍的是Reachability文件夹下的AFNetworkReachabilityManager类.通过字面意思我们就可以知道AFNetworkReachabilityManager是用来监测 ...
- AFNetworking2.0源码解析<三>
本篇说说安全相关的AFSecurityPolicy模块,AFSecurityPolicy用于验证HTTPS请求的证书,先来看看HTTPS的原理和证书相关的几个问题. HTTPS HTTPS连接建立过程 ...
- AFNetworking (3.1.0) 源码解析 <五>
这次主要开始讲解一下文件夹Serialization下的类AFURLRequestSerialization. AFURLRequestSerialization类遵守`AFURLRequestSer ...
- AFNetworking (3.1.0) 源码解析 <一>
首先说一下AFNetworking的github地址:GitHub - AFNetworking/AFNetworking: A delightful networking framework for ...
- AFNetworking (3.1.0) 源码解析 <四>
这次主要看一下文件夹Security中的类AFSecurityPolicy----安全策略类. AFSecurityPolicy主要的作用是验证HTTPS请求证书的有效性,在iOS9之后,默认不能发送 ...
- AFNetworking (3.1.0) 源码解析 <二>
这次讲解AFHTTPSessionManager类,按照顺序还是先看.h文件,注释中写到AFHTTPSessionManager是AFURLSessionManager的子类,并且带有方便的HTTP请 ...
- solr&lucene3.6.0源码解析(三)
solr索引操作(包括新增 更新 删除 提交 合并等)相关UML图如下 从上面的类图我们可以发现,其中体现了工厂方法模式及责任链模式的运用 UpdateRequestProcessor相当于责任链模式 ...
- Heritrix 3.1.0 源码解析(三十七)
今天有兴趣重新看了一下heritrix3.1.0系统里面的线程池源码,heritrix系统没有采用java的cocurrency包里面的并发框架,而是采用了线程组ThreadGroup类来实现线程池的 ...
- solr&lucene3.6.0源码解析(四)
本文要描述的是solr的查询插件,该查询插件目的用于生成Lucene的查询Query,类似于查询条件表达式,与solr查询插件相关UML类图如下: 如果我们强行将上面的类图纳入某种设计模式语言的话,本 ...
随机推荐
- IoC模式(控制反转)(转)
转自:http://www.cnblogs.com/qqlin/archive/2012/10/09/2707075.html,写的很好,用C#代码解释控制反转,然后更进一步,提到依赖注入是控制反转的 ...
- 面试后 follow up letter 分享
分享一下最近面试外企的follow up letter. Dear Mr. Xu, Thank you again for the time you and Mr. Guo spent wit ...
- ng-class css样式
<style> .error{background-color: red;} .warning{background-color: yellow;} </style> < ...
- EntityFramework - Migrations
EntityFramework - Migrations 對項目進行EF的數據庫升級操作.分爲開發環境與部署環境.上的操作總結. 引用: Command說明https://coding.abel.n ...
- asp.net mvc 强类型视图中传入List 数据到控制器
问题来源: 在和一位技术老师聊天时,老师问我一个mvc 表单提交的问题,问道:怎样在表单提交的时候,将 带有 List 属性的对象传入控制器? 这时,我有点呆了,以前一直都好像是 单一属性的表单提交, ...
- DataNode工作原理(四)
DataNode的作用:提供真实文件数据的存储服务.以文件块进行存储. 文件块(block):最基本的存储单位.对文件内容而言,一个文件的长度大小是size,那么从文件的0偏移开始,按照固定的大小,顺 ...
- STM32 枚举类型和结构体的使用
结构体就是一个可以包含不同数据类型的一个结构,它是一种可以自己定义的数据类型. 首先结构体可以在一个结构中声明不同的数据类型. 第二相同结构的结构体变量是可以相互赋值的,而 ...
- ios入门之c语言篇——基本函数——4——数值交换函数
一个常用函数,被整理出来,免得每次 都要写 参数返回值解析: 参数: *a:int*,需要交换值的第一个变量: *b:int*,需要交换值的第二个变量: 返回值: (无) 函数解析: swap(&am ...
- Qt tip 网络请求 QNetworkRequest QJason 处理 JSON
http://blog.csdn.net/linbounconstraint/article/details/52399415 http://download.csdn.net/detail/linb ...
- Apache HTTP Server mod_session_dbd模块mod_session_dbd.c 安全漏洞
漏洞名称: Apache HTTP Server mod_session_dbd模块mod_session_dbd.c 安全漏洞 CNNVD编号: CNNVD-201307-488 发布时间: 201 ...