当前位置: 首页 > news >正文

AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization

本篇是AFNetworking 3.0 源码解读的第四篇了。

AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager

AFNetworking 3.0 源码解读(二)之 AFSecurityPolicy

AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization

 

这次主要讲AFURLResponseSerialization(HTTP响应)这一个类的知识。

这是一个协议,只要遵守这个协议,就要实现NSSecureCoding/NSCopying这两个协议,还要实现

- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

这个方法来返回序列化后的结果。不管是下边的AFHTTPResponseSerializer,还是它的子类,都遵守这个协议,也就是在各自的实现中实现了这个协议,然后返回了属于自身的一个结果。

 

ps:根据这个协议,我有了一些启发。当我们在设计一个网络框架的时候,因为业务不同,返回的数据也有很多种,通常的一种做法是直接返回服务器响应的数据,由业务人员自己实现业务。但是如果业务繁杂,这样写出的代码也会很乱,我们不妨采用类似这种协议的设计模式,这样做有两个好处:

1. 业务人员和数据人员可以分开。 数据提前约定好名称和内容,写数据人员实现数据部分,写业务人员实现业务部分。

2. 左右的数据转换放到协议实现方法中,出现问题,更容易查找问题。

 

 

由于这个类有很多的子类,我们先来看看这些类的组成,然后逐一的对每个子类的代码进行解读。

 

==================================  分割线 =======================================

 我们还是先来看看AFHTTPResponseSerializer头文件的组成部分。

 

 

 

 

 

来看看实现部分:

这个初始化,看起来还是很普通的,主要是初始化一些默认值,我们在这里花点篇幅讲解一下NSIndexSet这个集合的知识。

定义:NSIndexSet是一个有序的,唯一的,无符号整数的集合。

我们先看个例子:

 1    NSMutableIndexSet *indexSetM = [NSMutableIndexSet indexSet];
 2     [indexSetM addIndex:19];
 3     [indexSetM addIndex:4];
 4     [indexSetM addIndex:6];
 5     [indexSetM addIndex:8];
 6     [indexSetM addIndexesInRange:NSMakeRange(20, 10)];
 7     
 8     [indexSetM enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
 9         NSLog(@"%lu",idx);
10     }];

打印结果如下:

2016-08-10 11:39:00.826 qikeyunDemo[3765:100078] 4
2016-08-10 11:39:00.827 qikeyunDemo[3765:100078] 6
2016-08-10 11:39:00.827 qikeyunDemo[3765:100078] 8
2016-08-10 11:39:00.827 qikeyunDemo[3765:100078] 19
2016-08-10 11:39:00.827 qikeyunDemo[3765:100078] 20
2016-08-10 11:39:00.828 qikeyunDemo[3765:100078] 21
2016-08-10 11:39:00.828 qikeyunDemo[3765:100078] 22
2016-08-10 11:39:00.828 qikeyunDemo[3765:100078] 23
2016-08-10 11:39:00.828 qikeyunDemo[3765:100078] 24
2016-08-10 11:39:00.828 qikeyunDemo[3765:100078] 25
2016-08-10 11:39:00.828 qikeyunDemo[3765:100078] 26
2016-08-10 11:39:00.828 qikeyunDemo[3765:100078] 27
2016-08-10 11:39:00.828 qikeyunDemo[3765:100078] 28
2016-08-10 11:39:00.829 qikeyunDemo[3765:100078] 29

这充分说明了一下几点:

1. 它是一个无符号整数的集合。

2. 使用addIndex方法可以添加单个整数值,使用addIndexesInRange可以添加一个范围,比如NSMakeRange(20, 10) 取20包括20后边十个整数。

3. 唯一性,集合内的整数不会重复出现。

具体用法就不再次做详细的解释了,有兴趣的朋友可以看看这篇文章: NSIndexSet 用法

 

因此 self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)]; 这个方法设置了默认接受的状态码范围为200 ~ 299.

 

 1 - (BOOL)validateResponse:(NSHTTPURLResponse *)response
 2                     data:(NSData *)data
 3                    error:(NSError * __autoreleasing *)error
 4 {
 5     // 1. 默认responseIsValid == yes
 6     BOOL responseIsValid = YES;
 7     NSError *validationError = nil;
 8 
 9     // 2. 假如response存在且类型是NSHTTPURLResponse
10     if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
11         
12         // 2.1 条件:self.acceptableContentTypes存在且不包含服务器返回的MIMEType且MIMEType和data都不能为空
13         if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
14             !([response MIMEType] == nil && [data length] == 0)) {
15 
16             if ([data length] > 0 && [response URL]) {
17                 
18                 // 2.1.1 生成错误信息
19                 NSMutableDictionary *mutableUserInfo = [@{
20                                                           NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
21                                                           NSURLErrorFailingURLErrorKey:[response URL],
22                                                           AFNetworkingOperationFailingURLResponseErrorKey: response,
23                     
24                                                           } mutableCopy];
25                 // 2.1.2 包含data错误信息
26                 if (data) {
27                     mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
28                 }
29 
30                 // 2.1.3 生成NSError
31                 validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
32             }
33 
34             responseIsValid = NO;
35         }
36 
37         // 同上
38         if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
39             NSMutableDictionary *mutableUserInfo = [@{
40                                                NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
41                                                NSURLErrorFailingURLErrorKey:[response URL],
42                                                AFNetworkingOperationFailingURLResponseErrorKey: response,
43                                        } mutableCopy];
44 
45             if (data) {
46                 mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
47             }
48 
49             // 设置错误,通过AFErrorWithUnderlyingError这个函数设置validationError的NSUnderlyingErrorKey
50             validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
51 
52             responseIsValid = NO;
53         }
54     }
55 
56     // 赋值
57     if (error && !responseIsValid) {
58         *error = validationError;
59     }
60 
61     return responseIsValid;
62 }

这个方法就是检测响应的有效性的。默认是YES。整个方法中比较值得关注的是对NSError的使用。在这里不对它做详细的介绍,大概解释下最长用的一些东东。

1. 我们关注下它的NSDictionary *userInfo这个属性,错误信息一般都在这个字典中获得。因为是一个字典,所以我们在给NSError的userInfo赋值的时候,会用到key。我们看看系统自带的key和含义有哪些?

 

当然我们也可以自定义key来操作NSError。

在上边的那个方法中,有可能会出现两个错误,在self.acceptableContentTypes和self.acceptableStatusCodes这两个判断中,如果都出现错误怎么办呢?

这就用到了NSUnderlyingErrorKey 这个字段,它标示一个优先的错误,value为NSErro对象。

通过下边的这个函数进行了转换和赋值:

 

 

 1 #pragma mark - NSSecureCoding
 2 
 3 + (BOOL)supportsSecureCoding {
 4     return YES;
 5 }
 6 
 7 - (instancetype)initWithCoder:(NSCoder *)decoder {
 8     self = [self init];
 9     if (!self) {
10         return nil;
11     }
12 
13     self.acceptableStatusCodes = [decoder decodeObjectOfClass:[NSIndexSet class] forKey:NSStringFromSelector(@selector(acceptableStatusCodes))];
14     self.acceptableContentTypes = [decoder decodeObjectOfClass:[NSIndexSet class] forKey:NSStringFromSelector(@selector(acceptableContentTypes))];
15 
16     return self;
17 }
18 
19 - (void)encodeWithCoder:(NSCoder *)coder {
20     [coder encodeObject:self.acceptableStatusCodes forKey:NSStringFromSelector(@selector(acceptableStatusCodes))];
21     [coder encodeObject:self.acceptableContentTypes forKey:NSStringFromSelector(@selector(acceptableContentTypes))];
22 }
23 
24 #pragma mark - NSCopying
25 
26 - (instancetype)copyWithZone:(NSZone *)zone {
27     AFHTTPResponseSerializer *serializer = [[[self class] allocWithZone:zone] init];
28     serializer.acceptableStatusCodes = [self.acceptableStatusCodes copyWithZone:zone];
29     serializer.acceptableContentTypes = [self.acceptableContentTypes copyWithZone:zone];
30 
31     return serializer;
32 }

这几个是对 NSCopying NSSecureCoding 这两个协议的实现部分,算是固定写法吧。为了节省篇幅 ,在下边的各个分类中,就不对这些代码进行说明了。

 

==================================  分割线 ==================================

我们来看看这个Json序列化的头文件。

 

 

这个选项可以设置json的读取选项,我们点进去可以看到:

  • NSJSONReadingMutableContainers    这个解析json成功后返回一个容器。

  • NSJSONReadingMutableLeaves         返回中的json对象中字符串为NSMutableString

  • NSJSONReadingAllowFragments        允许JSON字符串最外层既不是NSArray也不是NSDictionary,但必须是有效的JSON Fragment。例如使用这个选项可以解析 @“123” 这样的字符串

我们举个例子说明一下NSJSONReadingMutableContainers:

 1 NSString *str = @"{\"name\":\"zhangsan\"}";
 2     
 3     NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
 4     // 应用崩溃,不选用NSJSONReadingOptions,则返回的对象是不可变的,NSDictionary
 5     [dict setObject:@"male" forKey:@"sex"];
 6     
 7     NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil];
 8     // 没问题,使用NSJSONReadingMutableContainers,则返回的对象是可变的,NSMutableDictionary
 9     [dict setObject:@"male" forKey:@"sex"];
10     
11     NSLog(@"%@", dict);

如果服务器返回的json的最外层并不是以NSArray 或者 NSDictionary ,而是一个有效的json fragment ,比如 就返回了一个@"abc"。 那么我们使用NSJSONReadingAllowFragments这个选项也能够解析出来。

 

 

这个属性用来设置是否过滤NSNull。

 

 

通过初始化方法可以看出来,AFNetworking支持的ContentType有:

  • application/json

  • text/json

  • text/javascript

 

 1 - (id)responseObjectForResponse:(NSURLResponse *)response
 2                            data:(NSData *)data
 3                           error:(NSError *__autoreleasing *)error
 4 {
 5     // 判空处理,如果验证结果失败,在没有error 或者 错误中code:NSURLErrorCannotDecodeContentData 的情况下,是不能解析数据的,就返回nil
 6     if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
 7         if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
 8             return nil;
 9         }
10     }
11 
12     id responseObject = nil;
13     NSError *serializationError = nil;
14     // 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.
15     // See https://github.com/rails/rails/issues/1742
16     BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
17     if (data.length > 0 && !isSpace) {
18         responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
19     } else {
20         return nil;
21     }
22 
23     if (self.removesKeysWithNullValues && responseObject) {
24         responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
25     }
26 
27     if (error) {
28         *error = AFErrorWithUnderlyingError(serializationError, *error);
29     }
30 
31     return responseObject;
32     
33     
34 }

json转化的和新方法,这个方法中除了加了一些必要的判断之外,新出现了两个函数,函数的实现也比较好理解,就不做详细介绍了,把代码贴出来:

 1 // 检测错误或者有限错误中是否匹配code和domain
 2 static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
 3     
 4     // error中的domain和code相同,直接返回YES
 5     if ([error.domain isEqualToString:domain] && error.code == code) {
 6         return YES;
 7         
 8         // 否则检测error中的NSUnderlyingErrorKey
 9     } else if (error.userInfo[NSUnderlyingErrorKey]) {
10         return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
11     }
12 
13     return NO;
14 }
 1 // 该方法用于删除一个对象中的NUll
 2 static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
 3     
 4     // 1.数组
 5     if ([JSONObject isKindOfClass:[NSArray class]]) {
 6         
 7         //1.1 创建一个可变的数组,为了提高性能,使用Capacity创建
 8         NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
 9         
10         // 1.2 遍历数组,通过迭代的手段清空数组内的null
11         for (id value in (NSArray *)JSONObject) {
12             [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
13         }
14 
15         // 1.3 readingOptions == NSJSONReadingMutableContainers 返回可变容器,其他返回不可变容器
16         return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
17     }
18     
19     // 2. 思想同上
20     else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
21         NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
22         for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
23             id value = (NSDictionary *)JSONObject[key];
24             if (!value || [value isEqual:[NSNull null]]) {
25                 [mutableDictionary removeObjectForKey:key];
26             } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
27                 mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
28             }
29         }
30 
31         return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
32     }
33 
34     return JSONObject;
35 }

 

================================ 分割线 ====================================

这个分类是解析XML数据的。

 

支持的ContentType:

  • application/xml

  • text/xml

 

================================  分割线 =============================

 

注: 这个子类只在mac os x上使用

支持的ContentType:

  • application/xml

  • text/xml

 

 

=============================== 分隔线 =================================

这个分类用来把json解析成PropertyList:

支持的ContentType:

  • application/x-plist

 

=================================  分割线 ===============================

这是一个UIImage分类,添加了一个安全的NData转UIImage的方法;那么什么叫安全的呢?当我们访问或者操纵数据的时候,由于数据还可能被别人操纵,这就有可能出现不安全的情况,为了解决这个问题,引入NSLock这个锁对象 。 

使用方法:

  • 创建一个单例NSLock *lock
  • [lock lock]
  • do something...
  • [lock unlock]

 

这个私有方法,返回一个按照scale收缩的图片。这就使用到了上边的那个安全转换数据的方法了。但看这个方法我们应该知道下边两点知识:

  • image.images   images这个属性的用法,下边我们举例说明
  • - (instancetype)initWithCGImage:(CGImageRef)cgImage scale:(CGFloat)scale orientation:(UIImageOrientation)orientation NS_AVAILABLE_IOS(4_0);   这个是创建UIImage的一个方法,稍微注意下需要哪些参数。

1 UIImage *image0 = [UIImage imageNamed:@"SenderVoiceNodePlaying001"];
2     UIImage *image1 = [UIImage imageNamed:@"SenderVoiceNodePlaying002"];
3     UIImage *image2 = [UIImage imageNamed:@"SenderVoiceNodePlaying003"];
4     
5     self.imageView.image = [UIImage animatedImageWithImages:@[image0,image1,image2] duration:1.5];
6    

效果图如下:

那么这个images属性就可以应用到很多地方了,我们可以使用这个属性来生成一个类似gif的效果或者简单的动画是最常用的场景。

 

  1 // 根据响应结果和scale返回一张图片
  2 static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale) {
  3     
  4     // 1. 判空
  5     if (!data || [data length] == 0) {
  6         return nil;
  7     }
  8 
  9     // 2. 但凡带CG开头的,标示是CoreGraphics
 10     CGImageRef imageRef = NULL;
 11     
 12     // 3. CGDataProviderRef 可以细节为CoreGraphics中对data的包装
 13     CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
 14 
 15     // 判断响应返回的MIMEType类型,
 16     if ([response.MIMEType isEqualToString:@"image/png"]) {
 17         imageRef = CGImageCreateWithPNGDataProvider(dataProvider,  NULL, true, kCGRenderingIntentDefault);
 18     } else if ([response.MIMEType isEqualToString:@"image/jpeg"]) {
 19         imageRef = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
 20 
 21         if (imageRef) {
 22             CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(imageRef);
 23             CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(imageColorSpace);
 24 
 25             // CGImageCreateWithJPEGDataProvider does not properly handle CMKY, so fall back to AFImageWithDataAtScale
 26             if (imageColorSpaceModel == kCGColorSpaceModelCMYK) {
 27                 CGImageRelease(imageRef);
 28                 imageRef = NULL;
 29             }
 30         }
 31     }
 32 
 33     CGDataProviderRelease(dataProvider);
 34 
 35     UIImage *image = AFImageWithDataAtScale(data, scale);
 36     if (!imageRef) {
 37         if (image.images || !image) {
 38             return image;
 39         }
 40 
 41         imageRef = CGImageCreateCopy([image CGImage]);
 42         if (!imageRef) {
 43             return nil;
 44         }
 45     }
 46     
 47     // 代码到了这里,这个imageRef肯定有值了,有可能是response.MIMEType得到的,也有可能是根据image得到的
 48 
 49     size_t width = CGImageGetWidth(imageRef);
 50     size_t height = CGImageGetHeight(imageRef);
 51     size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
 52 
 53     // 这行代码不太明白什么意思。。
 54     if (width * height > 1024 * 1024 || bitsPerComponent > 8) {
 55         CGImageRelease(imageRef);
 56 
 57         return image;
 58     }
 59 
 60     // CGImageGetBytesPerRow() calculates incorrectly in iOS 5.0, so defer to CGBitmapContextCreate
 61     size_t bytesPerRow = 0;
 62     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
 63     CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);
 64     CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
 65 
 66     if (colorSpaceModel == kCGColorSpaceModelRGB) {
 67         uint32_t alpha = (bitmapInfo & kCGBitmapAlphaInfoMask);
 68 #pragma clang diagnostic push
 69 #pragma clang diagnostic ignored "-Wassign-enum"
 70         if (alpha == kCGImageAlphaNone) {
 71             bitmapInfo &= ~kCGBitmapAlphaInfoMask;
 72             bitmapInfo |= kCGImageAlphaNoneSkipFirst;
 73         } else if (!(alpha == kCGImageAlphaNoneSkipFirst || alpha == kCGImageAlphaNoneSkipLast)) {
 74             bitmapInfo &= ~kCGBitmapAlphaInfoMask;
 75             bitmapInfo |= kCGImageAlphaPremultipliedFirst;
 76         }
 77 #pragma clang diagnostic pop
 78     }
 79 
 80     CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
 81 
 82     CGColorSpaceRelease(colorSpace);
 83 
 84     if (!context) {
 85         CGImageRelease(imageRef);
 86 
 87         return image;
 88     }
 89 
 90     CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, width, height), imageRef);
 91     CGImageRef inflatedImageRef = CGBitmapContextCreateImage(context);
 92 
 93     CGContextRelease(context);
 94 
 95     UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation];
 96 
 97     CGImageRelease(inflatedImageRef);
 98     CGImageRelease(imageRef);
 99 
100     return inflatedImage;
101 }

上边的这个方法设计到了CoreGraphics的知识,这个还不是特别了解。稍后会补一补这方面的知识。

上边的代码有一点不明白的地方,:

1   // 这行代码不太明白什么意思。。
2     if (width * height > 1024 * 1024 || bitsPerComponent > 8) {
3         CGImageRelease(imageRef);
4 
5         return image;
6     }

 

后来看到了一篇文章 把原话粘贴出来

AFJSONResponseSerializer使用系统内置的NSJSONSerialization解析json,NSJSON只支持解析UTF8编码的数据(还有UTF-16LE之类的,都不常用),所以要先把返回的数据转成UTF8格式。这里会尝试用HTTP返回的编码类型和自己设置的stringEncoding去把数据解码转成字符串NSString,再把NSString用UTF8编码转成NSData,再用NSJSONSerialization解析成对象返回。

上述过程是NSData->NSString->NSData->NSObject,这里有个问题,如果你能确定服务端返回的是UTF8编码的json数据,那NSData->NSString->NSData这两步就是无意义的,而且这两步进行了两次编解码,很浪费性能,所以如果确定服务端返回utf8编码数据,就建议自己再写个JSONResponseSerializer,跳过这两个步骤。

此外AFJSONResponseSerializer专门写了个方法去除NSNull,直接把对象里值是NSNull的键去掉,还蛮贴心,若不去掉,上层很容易忽略了这个数据类型,判断了数据是否nil没判断是否NSNull,进行了错误的调用导致core。

图片解压

当我们调用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可以做这个事。

关于图片解压,还有几个问题不清楚:

1.本来以为调用imageWithData方法只是持有了数据,没有做解压相关的事,后来看到调用堆栈发现已经做了一些解压操作,从调用名字看进行了huffman解码,不知还会继续做到解码jpg的哪一步。 
UIImage_jpg

2.以上图片手动解压方式都是在CPU进行的,如果不进行手动解压,把图片放进layer里,让底层自动做这个事,是会用GPU进行的解压的。不知用GPU解压与用CPU解压速度会差多少,如果GPU速度很快,就算是在主线程做解压,也变得可以接受了,就不需要手动解压这样的优化了,不过目前没找到方法检测GPU解压的速度。

原来做这个转化的目的是为了尽量避免在主线程解压数据,因图像太大造成内存崩溃的问题。

============================== 分割线 ==================================

 

支持的ContentType:

  • 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

 

===========================  分割线 ==============================

 

 

这个复合型的子类有一个数组属性,里边装着多种序列化类型。看其实现方法也是,遍历数组中的内容,只要能转化成功,就返回数据。

 

============================= 分割线 =================================

 

好了,这篇就到此为止了,我们学到了

  • 通过一个协议来得到不同转换的结果。
  • 知道了AFNetworking响应结果支持的各种类型。
  • 大体了解了NSIndexSet的使用方法
  • 如果创建一个NSError 和 带有优先错误的NSUnderlyingErrorKey
  • 服务器返回的图片是压缩格式,要进行解压
  • 使用images来实现gif效果

 

相关文章:

  • 玩转Slot Machine
  • JavaScript之数组循环 forEach 循环输出数组元素
  • emacs初体验
  • RAW+ASM 的RAC 安装文档
  • 7 个 JavaScript “特性”
  • linux 下 ant 安装配置
  • 后台——使用maven时出现Failure to transfer 错误的解决方法
  • 吾爱论坛浏览器分享
  • java 极光推送
  • Plsql连接不上64位oracle数据库问题解决方案
  • 【面试系列】之二:关于js原型
  • 基于nexus的maven私服配置
  • 设计模式之Adapter模式
  • 关于KMP算法理解(快速字符串匹配)
  • uva 10370 Above Average
  • JS中 map, filter, some, every, forEach, for in, for of 用法总结
  • 【JavaScript】通过闭包创建具有私有属性的实例对象
  • 【跃迁之路】【444天】程序员高效学习方法论探索系列(实验阶段201-2018.04.25)...
  • 2017年终总结、随想
  • 230. Kth Smallest Element in a BST
  • If…else
  • JavaScript 基本功--面试宝典
  • JS函数式编程 数组部分风格 ES6版
  • PermissionScope Swift4 兼容问题
  • Python 使用 Tornado 框架实现 WebHook 自动部署 Git 项目
  • Redis在Web项目中的应用与实践
  • Ruby 2.x 源代码分析:扩展 概述
  • uva 10370 Above Average
  • V4L2视频输入框架概述
  • vue-router 实现分析
  • 大整数乘法-表格法
  • 动手做个聊天室,前端工程师百无聊赖的人生
  • 复杂数据处理
  • 工作中总结前端开发流程--vue项目
  • 看完九篇字体系列的文章,你还觉得我是在说字体?
  • 利用阿里云 OSS 搭建私有 Docker 仓库
  • 前嗅ForeSpider采集配置界面介绍
  • 驱动程序原理
  • 人脸识别最新开发经验demo
  • 数据库写操作弃用“SELECT ... FOR UPDATE”解决方案
  • ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTr
  • #### go map 底层结构 ####
  • (4)事件处理——(6)给.ready()回调函数传递一个参数(Passing an argument to the .ready() callback)...
  • (done) ROC曲线 和 AUC值 分别是什么?
  • (ZT) 理解系统底层的概念是多么重要(by趋势科技邹飞)
  • (二)学习JVM —— 垃圾回收机制
  • (未解决)jmeter报错之“请在微信客户端打开链接”
  • (中等) HDU 4370 0 or 1,建模+Dijkstra。
  • (转)http协议
  • (转)iOS字体
  • ***微信公众号支付+微信H5支付+微信扫码支付+小程序支付+APP微信支付解决方案总结...
  • .Net Attribute详解(上)-Attribute本质以及一个简单示例
  • .NET多线程执行函数
  • .NET高级面试指南专题十一【 设计模式介绍,为什么要用设计模式】
  • .NET开发人员必知的八个网站