zoukankan      html  css  js  c++  java
  • 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效果
  • 相关阅读:
    微信公众号分析
    微信自动聊天机器人
    使用itchat分析自己的微信(1)
    内容补充 ----- 易错点
    运算符优先级
    亡羊补牢系列之字符串格式化
    亡羊补牢之python基础语法
    python基础,构建一个301个字典库
    mysql每个表总的索引大小
    mysql 查看单个表每个索引的大小
  • 原文地址:https://www.cnblogs.com/machao/p/5755947.html
Copyright © 2011-2022 走看看