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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM