这次继续介绍文件夹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) 源码解析 <六>的更多相关文章

  1. AFNetworking (3.1.0) 源码解析 <三>

    今天要介绍的是Reachability文件夹下的AFNetworkReachabilityManager类.通过字面意思我们就可以知道AFNetworkReachabilityManager是用来监测 ...

  2. AFNetworking2.0源码解析<三>

    本篇说说安全相关的AFSecurityPolicy模块,AFSecurityPolicy用于验证HTTPS请求的证书,先来看看HTTPS的原理和证书相关的几个问题. HTTPS HTTPS连接建立过程 ...

  3. AFNetworking (3.1.0) 源码解析 <五>

    这次主要开始讲解一下文件夹Serialization下的类AFURLRequestSerialization. AFURLRequestSerialization类遵守`AFURLRequestSer ...

  4. AFNetworking (3.1.0) 源码解析 <一>

    首先说一下AFNetworking的github地址:GitHub - AFNetworking/AFNetworking: A delightful networking framework for ...

  5. AFNetworking (3.1.0) 源码解析 <四>

    这次主要看一下文件夹Security中的类AFSecurityPolicy----安全策略类. AFSecurityPolicy主要的作用是验证HTTPS请求证书的有效性,在iOS9之后,默认不能发送 ...

  6. AFNetworking (3.1.0) 源码解析 <二>

    这次讲解AFHTTPSessionManager类,按照顺序还是先看.h文件,注释中写到AFHTTPSessionManager是AFURLSessionManager的子类,并且带有方便的HTTP请 ...

  7. solr&lucene3.6.0源码解析(三)

    solr索引操作(包括新增 更新 删除 提交 合并等)相关UML图如下 从上面的类图我们可以发现,其中体现了工厂方法模式及责任链模式的运用 UpdateRequestProcessor相当于责任链模式 ...

  8. Heritrix 3.1.0 源码解析(三十七)

    今天有兴趣重新看了一下heritrix3.1.0系统里面的线程池源码,heritrix系统没有采用java的cocurrency包里面的并发框架,而是采用了线程组ThreadGroup类来实现线程池的 ...

  9. solr&lucene3.6.0源码解析(四)

    本文要描述的是solr的查询插件,该查询插件目的用于生成Lucene的查询Query,类似于查询条件表达式,与solr查询插件相关UML类图如下: 如果我们强行将上面的类图纳入某种设计模式语言的话,本 ...

随机推荐

  1. Android 新版NDK环境搭建(免Cygwin)

    使用最新ndk,直接抛弃cygwin,以前做Android的项目要用到NDK就必须要下载NDK,下载安装Cygwin(模拟Linux环境用的),下载CDT(Eclipse C/C++开发插件),还要配 ...

  2. POJ 2112.Optimal Milking (最大流)

    时间限制:2s 空间限制:30M 题意: 有K台挤奶机(编号1~K),C头奶牛(编号K+1~K+C),给出各点之间距离.现在要让C头奶牛到挤奶机去挤奶,每台挤奶机只能处理M头奶牛,求使所走路程最远的奶 ...

  3. vijos P1055奶牛浴场&& Winter Camp2002

    这道题是我在寒假的模拟赛里碰到的,现在想起来仍觉得余味无穷.题目大意大致如下:给你一个矩形并在其中划出一个最大的子矩形,当然,在这个矩形里有些地方是取不到的,也就是说我们划的这个子矩形不能包含这些点( ...

  4. javascript中的基本数据类型

    在javascipt中有五大基本数据类型,列表如下: 1.数字 他们又又包括(正负整数,浮点数)十进制数,十六进制数与八进制数,指数和特殊数值NaN,(Infinity,-Infinity)正负无穷 ...

  5. CSS3 :nth-child() 选择器

    CSS3 :nth-child() 选择器 代码: <!DOCTYPE html> <html> <head> <style> p:nth-child( ...

  6. apache 服务器端口被IIS服务器占用

    今天遇到一个问题 同事机子上安装了wamp运行环境,所有服务也打开了,把dede系统放到了www目录下面,但是打开localhost网址,一直是跳转到一个IIS的web服务器主页 想不到是什么问题,又 ...

  7. flask request

    请求对象要操作 URL (如 ?key=value )中提交的参数可以使用 args 属性:searchword = request.args.get('key', '')用户可能会改变 URL 导致 ...

  8. Mvc Controller 单元测试 Mock User.Identity.Name

    被测试的Action 包含 User.Identity.Name 代码,在写测试代码需要Mock ControllerContext对象 代码如下: var mock = new Mock<Co ...

  9. sdk manager更新失败,显示Download interrupted: read timed out,应该如何解决?

    今天开始第一天学习Android,就遇到一个大问题.sdk manager无法自动更新,总在提示超时!!! 经过网上查询发现问题原因是. 在利用android sdk manager更新时失败,具体情 ...

  10. Comparing randomized search and grid search for hyperparameter estimation

    Comparing randomized search and grid search for hyperparameter estimation Compare randomized search ...