iOS源码补完计划--AFNetworking(五)
目录
- 前言
- 核心代码
- AFURLResponseSerialization协议
- AFHTTPResponseSerializer
- AFJSONResponseSerializer
- AFXMLParserResponseSerializer
- AFXMLDocumentResponseSerializer
- AFPropertyListResponseSerializer
- AFImageResponseSerializer
- AFCompoundResponseSerializer
- APIDemo
- 参考资料
前言
AFNetworking源码第五篇、大概也是最后一篇
主要看了看AFURLResponseSerialization的内容
负责网络请求成功之后服务器返回的响应体进行格式化
代码乍看起来也挺多、但实际上大部分都是作为AFURLResponseSerialization
子类、将不同格式(JSON/XML/PList等格式
)分别处理的重复逻辑。读起来相对轻松很多。
但其中有很多小知识点。
AFN概述:《iOS源码补完计划--AFNetworking 3.1.0源码研读》
核心代码
-
AFURLResponseSerialization协议
协议中包含一个解码方法、所有的请求在结束时、都需要通过这个协议方法进行解码
首先、我们可以先看一下AFHTTPSessionManager
以及AFURLSessionManager
中的解析器属性。
/**
解码器、负责响应解码。比如json格式等等
*/
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
/**
网络请求返回的数据对象
*/
@property (nonatomic, strong) id <AFURLResponseSerialization> responseSerializer;
他们都遵循着一个协议AFURLResponseSerialization
、
就说明这个协议、是解码的关键。
点击进去:
/**
AFURLResponseSerialization协议、同时需要遵循NSSecureCoding, NSCopying两个协议
*/
@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>
/**
必须实现的方法
将响应体进行指定方式解码
@param response 需要处理的`NSURLResponse`
@param data 需要处理的数据
@param error 错误
@return 返回一个特定格式(Dic/Arr/Str/XML/Plist/图片等)
比如AFURLSessionManager中任务结束后对data的转码时就这样使用:
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
其中manager.responseSerializer为`AFJSONRequestSerializer`或者`AFPropertyListRequestSerializer`、均为`AFHTTPRequestSerializer(遵循AFURLResponseSerialization协议)`的子类。
*/
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end
搜索这个方法、果不其然。
只在AFURLSessionManager
==>AFURLSessionManagerTaskDelegate
==>- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
、也就是请求结束的方法中被调用了:
__block id responseObject = nil;
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
也就是说、只要你主动实现了这个代理方法、并且将它关联给AFURLSessionManager
或者AFHTTPSessionManager
。我们完全可以自定义出一种解析方式、只要在协议中执行即可。
这是协议代理一种很经典的用法。也是解耦的时候代替继承、然后重载父类方法时通用做法。
在多人协作的时候、约定好协议然后交由其他业务实现、也是提升开发效率很普遍的方式。
-
AFHTTPResponseSerializer
所有
AFResponseSerializer
解析器(AFJSONResponseSerializer
/AFXMLDocumentResponseSerializer
等等)的父类。对HTTP属性(HTTPCode
、Content-Type
)进行特化。
@interface AFHTTPResponseSerializer : NSObject <AFURLResponseSerialization>
//初始化
- (instancetype)init;
/**
解码器的编码方式
*/
@property (nonatomic, assign) NSStringEncoding stringEncoding;
/**
初始化
*/
+ (instancetype)serializer;
///-----------------------------------------
/// @name 配置响应解码器
///-----------------------------------------
/**
接受并解析的HTTP状态码。如果不为nil、未包含的状态码将不被解析
See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
*/
@property (nonatomic, copy, nullable) NSIndexSet *acceptableStatusCodes;
/**
接受并解析的Content-Type。如果不为nil、未包含的Content-Type将不被解析
*/
@property (nonatomic, copy, nullable) NSSet <NSString *> *acceptableContentTypes;
/**
检测响应能否被解析
@param response 响应
@param data 二进制文件
@param error 错误
@return 能否被解析
*/
- (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error;
@end
提供了编码方式、状态码集合、Content-Type集合、以及判断能否被解析的方法一个。
@implementation AFHTTPResponseSerializer
+ (instancetype)serializer {
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.stringEncoding = NSUTF8StringEncoding;
/*
NSIndexSet是一个无符号整数的集合、内部元素具有唯一性
将200 - 299的状态码全部添加
等同于[indexSetM addIndexesInRange:NSMakeRange(200, 100)];
*/
self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
self.acceptableContentTypes = nil;
return self;
}
初始化、没什么好说的。需要注意的是NSIndexSet
这个合集、是NSSet的数字版。使用的话注释里已经写了。
/**
检测响应能否被解析
@param response 响应
@param data 二进制文件
@param error 错误
@return 能否被解析
*/
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
BOOL responseIsValid = YES;
//验证的错误根据
NSError *validationError = nil;
//如果response 为 NSHTTPURLResponse实例
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
//设置了acceptableContentTypes && MIMEType(Content-Type)不允许接受 && MIMEType以及data 存在
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
!([response MIMEType] == nil && [data length] == 0)) {
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`对象是不存在主错误的。
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
}
responseIsValid = NO;
}
//设置了acceptableStatusCodes && statusCode(状态码)不允许接受 && 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`对象是不存在主错误的。
//如果两个判断都出错了、整合出来的`validationError`对象的主错误(error.userInfo[NSUnderlyingErrorKey])为之前content-type的error。本次error信息包含在userInfo里。
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
responseIsValid = NO;
}
}
//如果不被接受、将error传递
if (error && !responseIsValid) {
*error = validationError;
}
//返回是否有效
return responseIsValid;
}
这个方法可以检测当前返回的Request能否被响应
- 如果是
HTTPRequest
的话会根据MIMEType(Content-Type)
以及statusCode(状态码)
结合之前的两个属性集合进行判断。 - 如果不是
HTTPRequest
、直接跳过返回YES。
这也能很好的解释。为什么没有AFURLResponseSerializer
、而直接就是HTTPResponseSerializer
我们注意到、上面的两个判断都会产生NSError。
那么这两个NSError如何被一个**error捕获并且传递呢?
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;
//用主错误的domain、code、(包含优先错误的)新错误信息生成一个新错误。
return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}
其中用到了NSUnderlyingErrorKey
这个系统的key、代表优先错误。
如此、可以很容易的在NSError中嵌套另一个NSError。
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
//AFHTTPResponseSerializer 中这个数据转换的协议方法并不起什么作用、直接返回了data
return data;
}
这个方法看着没什么意义、不过确实也是...唯一的作用是为了当子类没有实现这个协议方法的时候、程序不会崩溃。
-
AFJSONResponseSerializer
针对JSON格式特化的解析器、继承
AFHTTPResponseSerializer
。
可以指定返回的对象(字典、数组、字符串、是否可变)。
还可以排空
/**
Json序列化
默认接收一下几种类型的content-type
- `application/json`
- `text/json`
- `text/javascript`
*/
@interface AFJSONResponseSerializer : AFHTTPResponseSerializer
//初始化
- (instancetype)init;
/**
读取JSON文件的选项 默认 0
typedef NS_OPTIONS(NSUInteger, NSJSONReadingOptions) {
NSJSONReadingMutableContainers = (1UL << 0),//返回一个MDic/MArr
NSJSONReadingMutableLeaves = (1UL << 1),//返回一个MStr
NSJSONReadingAllowFragments = (1UL << 2)//允许解析最外层不是Dic或者Arr的Json、比如@"123"
} API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
*/
@property (nonatomic, assign) NSJSONReadingOptions readingOptions;
/**
是否屏蔽NSNULL、默认为NO
*/
@property (nonatomic, assign) BOOL removesKeysWithNullValues;
/**
根据指定策略创建一个实例
*/
+ (instancetype)serializerWithReadingOptions:(NSJSONReadingOptions)readingOptions;
@end
为JSON格式的解析特化了一些属性
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
//默认可解析的content-type为'application/json", @"text/json", @"text/javascript'
self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
return self;
}
这里、利用父类(AFHTTPResponseSerializer
)属性acceptableContentTypes
指定了服务器返回的content-type
必须是JSON。
其他的子类也做了相同的操作。就不在一一指出了。
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
//检测这个响应是否可以被解析
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
//不能的话。
//如果error为空||
//error.code(或者error.userInfo[NSUnderlyingErrorKey]的主错误)=NSURLErrorCannotDecodeContentData
//(domain = AFURLResponseSerializationErrorDomain)的情况下是不能被解析的。
//总之就是上面那个是否可以被解析的判断出错了、并且按照正常流程搞出了error(也就是确定是contenttype或者httpcode不匹配)
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/1742
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;
}
//删除响应里的NSNULL(内部可以递归)
if (self.removesKeysWithNullValues && responseObject) {
responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
if (error) {
//整合error
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return responseObject;
}
实现的代理方法、先确定是否可以解析、然后进行排空操作
除了注释之外、需要注意的是还有两个函数。
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
//判断error中的domain和code是否符合
if ([error.domain isEqualToString:domain] && error.code == code) {
return YES;
} else if (error.userInfo[NSUnderlyingErrorKey]) {
//判断优先错误中domain和code是否符合
return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
}
return NO;
}
判断NSError符不符合对应code以及domain(也就是之前对contenttype或者httpcode判断出错时加进去的code以及domain)。用到了递归
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) {
//将数组里不为NULL的元素转译进来
[mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
}
//如果是NSJSONReadingMutableContainers、则返回一个可变的数组。否则返回一个不可变的
return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
} else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
//字典
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
//遍历所有key
for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
id value = (NSDictionary *)JSONObject[key];
if (!value || [value isEqual:[NSNull null]]) {
//value为空则移除
[mutableDictionary removeObjectForKey:key];
} else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
//如果value是数组或者字典、递归排空
mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
}
}
//如果是NSJSONReadingMutableContainers、则返回一个可变的字典。否则返回一个不可变的
return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
}
//不是数组也不是字典、返回原对象(应该就是个字符串了)
return JSONObject;
}
删除对象中的NSNULL
同样在其他子类里也会用到、不再一一指出
-
AFXMLParserResponseSerializer
XML解析器
@interface AFXMLParserResponseSerializer : AFHTTPResponseSerializer
@end
头文件里并没有什么新增的属性
//XML解析
- (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;
}
}
//返回XML对象
return [[NSXMLParser alloc] initWithData:data];
}
XML解析的协议实现
-
AFXMLDocumentResponseSerializer
也是解析XML
这个子类只在mac os x上使用
@interface AFXMLDocumentResponseSerializer : AFHTTPResponseSerializer
- (instancetype)init;
/**
Input and output options specifically intended for `NSXMLDocument` objects. For possible values, see the `NSJSONSerialization` documentation section "NSJSONReadingOptions". `0` by default.
*/
@property (nonatomic, assign) NSUInteger options;
/**
Creates and returns an XML document serializer with the specified options.
@param mask The XML document options.
*/
+ (instancetype)serializerWithXMLDocumentOptions:(NSUInteger)mask;
@end
- (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;
}
}
NSError *serializationError = nil;
NSXMLDocument *document = [[NSXMLDocument alloc] initWithData:data options:self.options error:&serializationError];
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return document;
}
MAC上解析XML的协议实现
-
AFPropertyListResponseSerializer
PList解析器
/**
PList序列化
支持以下MIME types:
- `application/x-plist`
*/
@interface AFPropertyListResponseSerializer : AFHTTPResponseSerializer
- (instancetype)init;
/**
PList 格式
typedef NS_ENUM(NSUInteger, NSPropertyListFormat) {
NSPropertyListOpenStepFormat = kCFPropertyListOpenStepFormat,
//指定属性列表文件格式为XML格式,仍然是纯文本类型,不会压缩文件
NSPropertyListXMLFormat_v1_0 = kCFPropertyListXMLFormat_v1_0,
//指定属性列表文件格式为二进制格式,文件是二进制类型,会压缩文件
NSPropertyListBinaryFormat_v1_0 = kCFPropertyListBinaryFormat_v1_0
//指定属性列表文件格式为ASCII码格式,对于旧格式的属性列表文件,不支持写入操作
};
*/
@property (nonatomic, assign) NSPropertyListFormat format;
/**
PList 读取选项
};
*/
@property (nonatomic, assign) NSPropertyListReadOptions readOptions;
/**
Creates and returns a property list serializer with a specified format, read options, and write options.
@param format The property list format.
@param readOptions The property list reading options.
*/
+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
readOptions:(NSPropertyListReadOptions)readOptions;
@end
将JSON解析成PList
- (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) {
//转化成NSPropertyListSerialization对象
responseObject = [NSPropertyListSerialization propertyListWithData:data options:self.readOptions format:NULL error:&serializationError];
}
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return responseObject;
}
PList解析的协议实现
-
AFImageResponseSerializer
图像格式化
/**
图像格式化
MIME types:
- `image/tiff`
- `image/jpeg`
- `image/gif`
- `image/png`
- `image/ico`
- `image/x-icon`
- `image/bmp`
- `image/x-bmp`
- `image/x-xbitmap`
- `image/x-win-bitmap`
*/
@interface AFImageResponseSerializer : AFHTTPResponseSerializer
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
/**
图片比例
*/
@property (nonatomic, assign) CGFloat imageScale;
/**
是否自动压缩图片(如PNG/JPEG)
当使用`setCompletionBlockWithSuccess:failure:`时、这个选项可以显著的提高性能
默认YES
*/
@property (nonatomic, assign) BOOL automaticallyInflatesResponseImage;
#endif
@end
解析图片的子类.可以设置图片比例、以及是否在AFN线程解压图片
- (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
if (self.automaticallyInflatesResponseImage) {
//自动解压
return AFInflatedImageFromResponseWithDataAtScale((NSHTTPURLResponse *)response, data, self.imageScale);
} else {
//否则只改变比例
return AFImageWithDataAtScale(data, self.imageScale);
}
#else
// Ensure that the image is set to it's correct pixel width and height
NSBitmapImageRep *bitimage = [[NSBitmapImageRep alloc] initWithData:data];
NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize([bitimage pixelsWide], [bitimage pixelsHigh])];
[image addRepresentation:bitimage];
return image;
#endif
return nil;
}
这里我们发现有两个函数
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];
}
直接用NSData生成UIImage
static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale) {
if (!data || [data length] == 0) {
return nil;
}
CGImageRef imageRef = NULL;
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
//判断响应返回的MIMEType类型,
if ([response.MIMEType isEqualToString:@"image/png"]) {
//PNG
imageRef = CGImageCreateWithPNGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
} else if ([response.MIMEType isEqualToString:@"image/jpeg"]) {
//JPEG
imageRef = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
if (imageRef) {
CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(imageRef);
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(imageColorSpace);
// CGImageCreateWithJPEGDataProvider does not properly handle CMKY, so fall back to AFImageWithDataAtScale
if (imageColorSpaceModel == kCGColorSpaceModelCMYK) {
CGImageRelease(imageRef);
imageRef = NULL;
}
}
}
CGDataProviderRelease(dataProvider);
UIImage *image = AFImageWithDataAtScale(data, scale);
if (!imageRef) {
if (image.images || !image) {
return image;
}
imageRef = CGImageCreateCopy([image CGImage]);
if (!imageRef) {
return nil;
}
}
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
if (width * height > 1024 * 1024 || bitsPerComponent > 8) {
CGImageRelease(imageRef);
return image;
}
// CGImageGetBytesPerRow() calculates incorrectly in iOS 5.0, so defer to CGBitmapContextCreate
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 inflatedImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation];
CGImageRelease(inflatedImageRef);
CGImageRelease(imageRef);
return inflatedImage;
}
这个函数具体的写法设计很多CG层面的代码、并不了解。
但作用查到了:
这里返回的
UIImage
是含有bitmap
数据的(因为通过解压出的bitmap
绘制出的CGImage
)
bitmap
的作用在于在将UIImage
交付给UIImageView
的时候。如果没有bitmap
将会自动解压一次。
在这里提前解压了、也就不会再主线程做出解压的操作。
-
AFCompoundResponseSerializer
可以包含多个解析器的复合解析器
@interface AFCompoundResponseSerializer : AFHTTPResponseSerializer
/**
解析器数组
*/
@property (readonly, nonatomic, copy) NSArray <id<AFURLResponseSerialization>> *responseSerializers;
/**
初始化
*/
+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray <id<AFURLResponseSerialization>> *)responseSerializers;
@end
你可以使用多个实现了AFURLResponseSerialization
(解码)协议的解析器来初始化它。
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
//遍历所有解析器、那个能解析就用哪个
for (id <AFURLResponseSerialization> serializer in self.responseSerializers) {
if (![serializer isKindOfClass:[AFHTTPResponseSerializer class]]) {
continue;
}
NSError *serializerError = nil;
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];
}
内部会遍历所有的解析器、哪个能解析当前响应体、就用哪个解析。
APIDemo
说实话两个文件加起来两千多行、每行都想顾及到我自己都不知道怎么写...但是把很多代码都进行了注释、有兴趣可以自取:
GitHub
参考资料
AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization
Plist 文件的优化