本篇是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效果