该文章阅读的AFNetworking的版本为3.2.0。javascript
AFURLResponseSerialization
这个类是用来解析服务器返回的数据,根据服务器返回数据的不一样,解析数据的类也不一样。java
AFURLResponseSerialization
这个协议只定义了一个方法,这个方法将服务器返回的数据解析后进行返回。git
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
复制代码
这七个类又分为了一个基类和六个子类,先来看基类。github
这个类是其余六个类的基类。json
/**
字符串编码方式,默认为NSUTF8StringEncoding
*/
@property (nonatomic, assign) NSStringEncoding stringEncoding;
/**
可接受的状态码集合,若是返回的状态码不在集合中则会验证时报错
*/
@property (nonatomic, copy, nullable) NSIndexSet *acceptableStatusCodes;
/**
可接受的Content-Type集合,若是返回的类型不在集合中则会验证时报错
*/
@property (nonatomic, copy, nullable) NSSet <NSString *> *acceptableContentTypes;
复制代码
/**
实例化方法
*/
+ (instancetype)serializer;
/**
初始化方法
*/
- (instancetype)init;
/**
验证服务器返回的数据
*/
- (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error;
复制代码
+ (instancetype)serializer {
// 普通的实例化方法
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
// 初始化属性
self.stringEncoding = NSUTF8StringEncoding;
// 可接受的状态码为200~299之间
self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
self.acceptableContentTypes = nil;
return self;
}
#pragma mark -
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
// 设置临时变量
BOOL responseIsValid = YES;
NSError *validationError = nil;
// 若是response有值,而且response是NSHTTPURLResponse类型的对象
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
// 若是设置了可接受的ContentTypes,而且响应的content-type不在可接受的范围内,而且响应的content-type有值或者返回了数据
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
!([response MIMEType] == nil && [data length] == 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;
}
// 若是设置了可接受状态码,而且响应的状态码不在可接受的范围内,而且响应有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;
}
}
// 若是传了NSError对象,而且响应无效,就赋值后返回错误信息
if (error && !responseIsValid) {
*error = validationError;
}
return responseIsValid;
}
复制代码
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
// 做为基类,仅仅实现了对响应和数据的验证
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
return data;
}
复制代码
+ (BOOL)supportsSecureCoding {
// 若是一个类符合 NSSecureCoding 协议并在 + supportsSecureCoding 返回 YES,就声明了它能够处理自己实例的编码解码方式,以防止替换攻击。
return YES;
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [self init];
if (!self) {
return nil;
}
self.acceptableStatusCodes = [decoder decodeObjectOfClass:[NSIndexSet class] forKey:NSStringFromSelector(@selector(acceptableStatusCodes))];
self.acceptableContentTypes = [decoder decodeObjectOfClass:[NSIndexSet class] forKey:NSStringFromSelector(@selector(acceptableContentTypes))];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.acceptableStatusCodes forKey:NSStringFromSelector(@selector(acceptableStatusCodes))];
[coder encodeObject:self.acceptableContentTypes forKey:NSStringFromSelector(@selector(acceptableContentTypes))];
}
复制代码
- (instancetype)copyWithZone:(NSZone *)zone {
AFHTTPResponseSerializer *serializer = [[[self class] allocWithZone:zone] init];
serializer.acceptableStatusCodes = [self.acceptableStatusCodes copyWithZone:zone];
serializer.acceptableContentTypes = [self.acceptableContentTypes copyWithZone:zone];
return serializer;
}
复制代码
经过这个类的命名能够直观的看出这个类是用来解析返回数据为JSON格式的。数组
/**
json数据解析选项,默认是0
*/
@property (nonatomic, assign) NSJSONReadingOptions readingOptions;
/**
是否删除value为NSNull的key,默认是否
*/
@property (nonatomic, assign) BOOL removesKeysWithNullValues;
复制代码
/**
初始化方法
*/
- (instancetype)init;
/**
指定json解析选项的实例化工厂方法
*/
+ (instancetype)serializerWithReadingOptions:(NSJSONReadingOptions)readingOptions;
复制代码
+ (instancetype)serializer {
// 重写父类方法,以不选择json解析选项的方式调用了本身的实例化工厂方法
return [self serializerWithReadingOptions:(NSJSONReadingOptions)0];
}
+ (instancetype)serializerWithReadingOptions:(NSJSONReadingOptions)readingOptions {
// 实例化本类对象指定json解析选项并返回
AFJSONResponseSerializer *serializer = [[self alloc] init];
serializer.readingOptions = readingOptions;
return serializer;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
// 设置了可接受的Content-Type
self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
return self;
}
复制代码
- (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;
}
}
// 为了解决在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.
// See https://github.com/rails/rails/issues/1742
BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
if (data.length == 0 || isSpace) {
return nil;
}
// 解析服务器返回的二级制数据
NSError *serializationError = nil;
id responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
// 若是没有解析后的数据就直接返回
if (!responseObject)
{
// 若是传入了错误信息参数
if (error) {
// 就将传入的错误信息添加到serializationError的NSUnderlyingErrorKey字段中
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return nil;
}
// 若是想要过滤NSNull,就进行过滤
if (self.removesKeysWithNullValues) {
return AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
return responseObject;
}
复制代码
同2.1.4安全
同2.1.5bash
当服务器返回的数据类型为XML时,用这个类进行解析,但这个类只返回NSXMLParser对象,具体的解析还要经过实现代理手动解析服务器
接口部分没有申明新的属性和方法网络
+ (instancetype)serializer {
// 实例化对象
AFXMLParserResponseSerializer *serializer = [[self alloc] init];
return serializer;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
// 设置了可接受的Content-Type
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/xml", @"text/xml", nil];
return self;
}
复制代码
- (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;
}
}
// 返回NSXMLParser对象
return [[NSXMLParser alloc] initWithData:data];
}
复制代码
这个类一样是用来解析当服务器返回的数据类型为XML,可是这个类用在MAC上,一样也是只返回NSXMLDocument对象,具体要手动解析
/**
NSXMLDocument对象输入输出的选项,默认为0
*/
@property (nonatomic, assign) NSUInteger options;
复制代码
/**
初始化方法
*/
- (instancetype)init;
/**
实例化对象的工厂方法
*/
+ (instancetype)serializerWithXMLDocumentOptions:(NSUInteger)mask;
复制代码
+ (instancetype)serializer {
// 调用本类的实例化工厂方法
return [self serializerWithXMLDocumentOptions:0];
}
+ (instancetype)serializerWithXMLDocumentOptions:(NSUInteger)mask {
// 实例化对象并设置参数
AFXMLDocumentResponseSerializer *serializer = [[self alloc] init];
serializer.options = mask;
// 返回对象
return serializer;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
// 设置了可接受的Content-Type
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/xml", @"text/xml", nil];
return self;
}
复制代码
- (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;
}
}
// 实例化NSXMLDocument对象
NSError *serializationError = nil;
NSXMLDocument *document = [[NSXMLDocument alloc] initWithData:data options:self.options error:&serializationError];
// 若是实例化没有成功就直接返回
if (!document)
{
// 若是传入了错误信息参数
if (error) {
// 就将传入的错误信息添加到serializationError的NSUnderlyingErrorKey字段中
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return nil;
}
return document;
}
复制代码
同2.1.4
同2.1.5
这个类是用来将服务器返回的数据解析为plist类型
/**
指定返回的plist格式,但即便设置了也没有用,由于在实现中并没用用到这个参数而是直接设置为NULL
*/
@property (nonatomic, assign) NSPropertyListFormat format;
/**
建立plist的可变选项
*/
@property (nonatomic, assign) NSPropertyListReadOptions readOptions;
复制代码
/**
初始化方法
*/
- (instancetype)init;
/**
实例化对象的工厂方法
*/
+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
readOptions:(NSPropertyListReadOptions)readOptions;
复制代码
+ (instancetype)serializer {
// 调用本类的实例化工厂方法
return [self serializerWithFormat:NSPropertyListXMLFormat_v1_0 readOptions:0];
}
+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
readOptions:(NSPropertyListReadOptions)readOptions
{
// 实例化对象并设置参数
AFPropertyListResponseSerializer *serializer = [[self alloc] init];
serializer.format = format;
serializer.readOptions = readOptions;
return serializer;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
// 设置了可接受的Content-Type
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/x-plist", nil];
return self;
}
复制代码
- (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 (!data) {
return nil;
}
// 实例化NSPropertyListSerialization对象解析data
NSError *serializationError = nil;
id responseObject = [NSPropertyListSerialization propertyListWithData:data options:self.readOptions format:NULL error:&serializationError];
// 若是没有解析成功就直接返回
if (!responseObject)
{
if (error) {
// 就将传入的错误信息添加到serializationError的NSUnderlyingErrorKey字段中
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return nil;
}
return responseObject;
}
复制代码
同2.1.4
同2.1.5
这个类是用来解析服务器返回的图片数据的
/**
图片的缩放因子,默认为屏幕的缩放因子
*/
@property (nonatomic, assign) CGFloat imageScale;
/**
是否对服务器返回的图片进行自动解压,默认为自动解压
*/
@property (nonatomic, assign) BOOL automaticallyInflatesResponseImage;
复制代码
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
// 设置了自身的属性
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"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", nil];
#if TARGET_OS_IOS || TARGET_OS_TV
self.imageScale = [[UIScreen mainScreen] scale];
self.automaticallyInflatesResponseImage = YES;
#elif TARGET_OS_WATCH
self.imageScale = [[WKInterfaceDevice currentDevice] screenScale];
self.automaticallyInflatesResponseImage = YES;
#endif
return self;
}
复制代码
- (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);
}
// 在MAC上能够直接调用系统方法解压
#else
// Ensure that the image is set to it is 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;
}
复制代码
同2.1.4
同2.1.5
这个类能够用来解析多个不一样类型的数据类型
/**
响应解析对象数组
*/
@property (readonly, nonatomic, copy) NSArray <id<AFURLResponseSerialization>> *responseSerializers;
复制代码
/**
实例化工厂方法
*/
+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray <id<AFURLResponseSerialization>> *)responseSerializers;
复制代码
/**
类扩展中只有一个属性用于保存传入的响应解对象数组
*/
@property (readwrite, nonatomic, copy) NSArray *responseSerializers;
复制代码
+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray *)responseSerializers {
// 实例化本类对象并设置属性
AFCompoundResponseSerializer *serializer = [[self alloc] init];
serializer.responseSerializers = responseSerializers;
return serializer;
}
复制代码
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
// 遍历传入的响应解析对象数组
for (id <AFURLResponseSerialization> serializer in self.responseSerializers) {
// 若是不是AFHTTPResponseSerializer类或者其子类就跳过进行下一个
if (![serializer isKindOfClass:[AFHTTPResponseSerializer class]]) {
continue;
}
// 调用其对应的解析方法,解析成功则返回
NSError *serializerError = nil;
id responseObject = [serializer responseObjectForResponse:response data:data error:&serializerError];
if (responseObject) {
// 若是有错误就将传入的错误信息添加到serializationError的NSUnderlyingErrorKey字段中
if (error) {
*error = AFErrorWithUnderlyingError(serializerError, *error);
}
return responseObject;
}
}
// 若是传入的响应解析对象都解析不了就调用其父类进行解析
return [super responseObjectForResponse:response data:data error:error];
}
复制代码
同2.1.4
同2.1.5
/**
用来识别是AFURLResponseSerialization对象解析的错误
*/
FOUNDATION_EXPORT NSString * const AFURLResponseSerializationErrorDomain;
/**
用来识别是AFURLResponseSerialization对象解析错误中的服务器返回的NSURLResponse对象
*/
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseErrorKey;
/**
用来识别是AFURLResponseSerialization对象解析错误中的服务器返回的NSData对象
*/
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey;
复制代码
NSString * const AFURLResponseSerializationErrorDomain = @"com.alamofire.error.serialization.response";
NSString * const AFNetworkingOperationFailingURLResponseErrorKey = @"com.alamofire.serialization.response.error.response";
NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey = @"com.alamofire.serialization.response.error.data";
复制代码
/**
该方法用于将一个NSError对象做为另外一个NSError对象的附属NSError对象,并将其放到userInfo属性NSUnderlyingErrorKey键对应的值中
*/
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
// 没有error为空的话就直接返回underlyingError
if (!error) {
return underlyingError;
}
// 若是underlyingError为空或者error中已经有附属underlyingError就直接返回error
if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
return error;
}
// 获取error的userInfo,并将underlyingError做为键NSUnderlyingErrorKey对应的值赋给error
NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;
return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}
/**
该方法用于判断error或者underlyingError是否为指定code和domain的erro
*/
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
// 若是error符合指定条件则返回YES
if ([error.domain isEqualToString:domain] && error.code == code) {
return YES;
// 若是error不符合可是有underlyingError,则递归调用本方法
} else if (error.userInfo[NSUnderlyingErrorKey]) {
return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
}
// 若是都不知足就返回NO
return NO;
}
/**
该方法用于过滤解析后的json数据中NSDictionary类型数据值为Null的键
*/
static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
// 若是解析后的json数据是NSArray类型的
if ([JSONObject isKindOfClass:[NSArray class]]) {
// 遍历元素若是仍是NSArray类型的就递归调用本方法直至元素不为NSArray类型
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
for (id value in (NSArray *)JSONObject) {
[mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
}
// 根据传入参数的可变性来返回对应可变性的NSArray对象
return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
// 若是解析后的json数据是NSDictionary类型的
} else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
// 遍历全部的key并取出对应的value
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
id value = (NSDictionary *)JSONObject[key];
// 若是value不存在或者是NSNull类型的对象,就将其移除
if (!value || [value isEqual:[NSNull null]]) {
[mutableDictionary removeObjectForKey:key];
// 若是value是NSArray或者NSDictionary类型的对象就递归调用本方法直至元素不为NSArray或者NSDictionary类型的对象
} else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
}
}
// 根据传入参数的可变性来返回对应可变性的NSDictionary对象
return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
}
// 若是不是是NSArray或者NSDictionary类型的对象就直接返回
return JSONObject;
}
/**
生成对应缩放因子的图片
*/
static UIImage * AFImageWithDataAtScale(NSData *data, CGFloat scale) {
// 根据二进制数据安全生成图片对象
UIImage *image = [UIImage af_safeImageWithData:data];
// 若是是gif图就直接返回图片对象
if (image.images) {
return image;
}
// 生成对应缩放因子的图片
return [[UIImage alloc] initWithCGImage:[image CGImage] scale:scale orientation:image.imageOrientation];
}
/**
生成对应缩放因子的图片并进行解压
*/
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);
// 若是是png格式直接解压
if ([response.MIMEType isEqualToString:@"image/png"]) {
imageRef = CGImageCreateWithPNGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
// 若是是jpg格式
} else if ([response.MIMEType isEqualToString:@"image/jpeg"]) {
// 先进行解压
imageRef = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
// 若是解压成功
if (imageRef) {
CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(imageRef);
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(imageColorSpace);
// 若是jpg的色彩空间是CMKY而不是RGB的话,不进行解压
// 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) {
// 若是是gif图或者没有生成图片就直接返回
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);
// 若是图片太大或者一个像素占用的字节超过8就不解压了
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;
}
复制代码
为何要费这么大劲解压图片呢?当咱们调用UIImage
的方法imageWithData:
方法把数据转成UIImage
对象后,其实这时UIImage
对象还没准备好须要渲染到屏幕的数据,如今的网络图像PNG和JPG都是压缩格式,须要把它们解压转成bitmap后才能渲染到屏幕上,若是不作任何处理,当你把UIImage
赋给UIImageView
,在渲染以前底层会判断到UIImage
对象未解压,没有bitmap数据,这时会在主线程对图片进行解压操做,再渲染到屏幕上。这个解压操做是比较耗时的,若是任由它在主线程作,可能会致使速度慢UI卡顿的问题。
AFImageResponseSerializer
除了把返回数据解析成UIImage
外,还会把图像数据解压,这个处理是在子线程(AFNetworking
专用的一条线程,详见AFURLConnectionOperation
),处理后上层使用返回的UIImage
在主线程渲染时就不须要作解压这步操做,主线程减轻了负担,减小了UI卡顿问题。
具体实现上在AFInflatedImageFromResponseWithDataAtScale
里,建立一个画布,把UIImage
画在画布上,再把这个画布保存成UIImage
返回给上层。只有JPG和PNG才会尝试去作解压操做,期间若是解压失败,或者遇到CMKY颜色格式的jpg,或者图像太大(解压后的bitmap太占内存,一个像素3-4字节,搞很差内存就爆掉了),就直接返回未解压的图像。
另外在代码里看到iOS才须要这样手动解压,MacOS上已经有封装好的对象NSBitmapImageRep能够作这个事。以上解释摘自这篇博客
/**
利用二级制数据安全生成图片对象的方法
*/
+ (UIImage *)af_safeImageWithData:(NSData *)data;
复制代码
/**
声明了一个锁对象用于安全生成图片
*/
static NSLock* imageLock = nil;
复制代码
+ (UIImage *)af_safeImageWithData:(NSData *)data {
// 声明图片变量
UIImage* image = nil;
// 只实例化一遍锁对象
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
imageLock = [[NSLock alloc] init];
});
// 安全的利用二级制数据生成图片对象
[imageLock lock];
image = [UIImage imageWithData:data];
[imageLock unlock];
return image;
}
复制代码
源码阅读系列:AFNetworking
源码阅读:AFNetworking(二)——AFURLRequestSerialization
源码阅读:AFNetworking(三)——AFURLResponseSerialization
源码阅读:AFNetworking(四)——AFSecurityPolicy
源码阅读:AFNetworking(五)——AFNetworkReachabilityManager
源码阅读:AFNetworking(六)——AFURLSessionManager
源码阅读:AFNetworking(七)——AFHTTPSessionManager
源码阅读:AFNetworking(八)——AFAutoPurgingImageCache
源码阅读:AFNetworking(九)——AFImageDownloader
源码阅读:AFNetworking(十)——AFNetworkActivityIndicatorManager
源码阅读:AFNetworking(十一)——UIActivityIndicatorView+AFNetworking
源码阅读:AFNetworking(十二)——UIButton+AFNetworking
源码阅读:AFNetworking(十三)——UIImageView+AFNetworking
源码阅读:AFNetworking(十四)——UIProgressView+AFNetworking