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效果