概述
上篇文章分析了AFURLRequestSerialization,本篇文章主要分析一下AFURLResponseSerialization。AFURLResponseSerialization主要負責對網絡請求回來的響應報文數據進行反序列化。主要的類包括AFHTTPResponseSerializer及其子類。
AFHTTPResponseSerializer
下面是AFHTTPResponseSerializer的相關屬性:
@interface AFHTTPResponseSerializer : NSObject <AFURLResponseSerialization>
@property (nonatomic, assign) NSStringEncoding stringEncoding; //文本編碼
@property (nonatomic, copy, nullable) NSIndexSet *acceptableStatusCodes; //允許的http狀態碼
@property (nonatomic, copy, nullable) NSSet *acceptableContentTypes; //允許的content-type類型
@end
stringEncoding屬性是文本編碼方式,用于響應的報文數據反序列化成字符串。acceptableStatusCodes是一個集合,表示客戶端可以接受的報文數據的http狀態碼。acceptableContentTypes是一個集合,表示客戶端可以接受的報文數據的content-type類型。接下來看一下初始化的代碼:
self.stringEncoding = NSUTF8StringEncoding;
self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)]; //允許200-299
self.acceptableContentTypes = nil;
默認指定文本編碼方式為UTF-8,acceptableStatusCodes的范圍是200-299,即HTTP響應狀態碼是200-299,因為根據RFC相關文檔規定,2字頭的狀態碼表示成功,下面是狀態碼的分類:
消息(1字頭)、成功(2字頭)、重定向(3字頭)請求錯誤(4字頭)、服務器錯誤(5、6字頭)
acceptableContentTypes為nil,允許任何content-type類型的報文數據。解析響應報文數據的方法是:
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
return data;
}
該方法首先驗證報文數據,如果驗證失敗,會生成錯誤信息傳給error,然后直接返回報文數據。下面是驗證方法的代碼注釋:
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
BOOL responseIsValid = YES;
NSError *validationError = nil;
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]]) {
...//生成錯誤信息
responseIsValid = NO;
}
if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
...//生成錯誤信息
responseIsValid = NO;
}
}
...
return responseIsValid;
}
該方法首先判斷響應的報文數據是否是NSHTTPURLResponse類型,即HTTP的相應報文,如果是,然后判斷報文數據的content-type是否在acceptableContentTypes范圍內,如果不在,生成錯誤信息,錯誤碼是NSURLErrorCannotDecodeContentData,即反序列化報文數據失敗。然后判斷是否在acceptableStatusCodes范圍內,如果不在,說明HTTP請求異常,生成錯誤信息,錯誤碼是NSURLErrorBadServerResponse,即服務器返回的報文數據有問題。
AFJSONResponseSerializer
AFJSONResponseSerializer是AFHTTPResponseSerializer的子類,專門用于解析JSON格式的響應報文數據,初始化方法如下:
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
return self;
}
該方法在父類方法的基礎上,規定acceptableContentTypes包含"application/json"、"text/json"、"text/javascript"三種JSON格式,即報文數據的content-type只能是JSON相關的格式。重寫了父類的解析方法,代碼注釋如下:
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
...
id responseObject = nil;
NSError *serializationError = nil;
@autoreleasepool { //反序列化JSON報文數據
NSString *responseString = [[NSString alloc] initWithData:data encoding:stringEncoding];
if (responseString && ![responseString isEqualToString:@" "]) {
data = [responseString dataUsingEncoding:NSUTF8StringEncoding];
if (data) {
if ([data length] > 0) {
responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
} else {
return nil;
}
} else {
//生成錯誤信息
}
}
}
//針對反序列化后的對象,進行NSNull過濾
if (self.removesKeysWithNullValues && responseObject) {
responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return responseObject;
}
該方法首先驗證響應報文數據,如果content-type不是JSON相關或者HTTP狀態碼不是成功類型,驗證不通過,直接返回nil。然后調用NSJSONSerialization反序列化報文成OC對象responseObject。接著根據removesKeysWithNullValues屬性做NSNull類型的過濾,層層遍歷responseObject的結構,如果是字典屬性,且value是NSNull,則從字典中刪除相應key/value。最后將處理后的responseObject返回。
AFXMLParserResponseSerializer
AFXMLParserResponseSerializer負責解析XML格式的報文數據,初始化方法指定了content-type是"application/xml","text/xml",即只支持XML格式的報文數據,然后調用解析方法,代碼如下:
- (id)responseObjectForResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
...//驗證content-type和statusCode
return [[NSXMLParser alloc] initWithData:data]; //生成XML解析器
}
該方法采用SAX的方式進行解析,特點是逐行解析,需要指定delegate,調用parse方法解析,當解析到每個節點的時候,都會通過delegate回調給調用層。這種方式讀取部分XML的時候就可以邊處理,不用等到XML加載完畢。
AFPropertyListResponseSerializer
plist格式類似XML格式,AFPropertyListResponseSerializer負責解析plist格式的報文數據,初始化方法指定了content-type是"application/x-plist",即只支持plist格式的報文數據,然后調用解析方法,代碼如下:
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
...//驗證content-type和statusCode
id responseObject;
NSError *serializationError = nil;
if (data) {
responseObject = [NSPropertyListSerialization propertyListWithData:data options:self.readOptions format:NULL error:&serializationError];
}
return responseObject;
}
該方法調用NSPropertyListSerialization的propertyListWithData:options:format:error:方法解析報文數據,最后將解析后的responseObject返回。
AFImageResponseSerializer
AFImageResponseSerializer是AFNetworking庫中專門用于解析圖片格式數據的類,負責將報文數據反序列化成UIImage對象返回,首先初始化方法中指定了acceptableContentTypes都是image相關的格式。automaticallyInflatesResponseImage屬性用于指定是否預處理圖片數據。通常情況下網絡請求返回的報文數據data,調用[UIImage imageWithData:data]得到的圖像是PNG或者JPG等壓縮格式的,如果將UIImage渲染到UIImageView上面,首先會進行解壓,然后渲染到屏幕上,這一處理在主線程完成。如果在子線程中預先解壓圖片,返回Bitmap格式的UIImage對象,這樣主線程只要渲染圖片即可,減輕了主線程的負擔。具體可以參考JSPatch大神博客中的分析。
具體負責解壓處理的方法是AFInflatedImageFromResponseWithDataAtScale,將PNG或者JPG格式的圖片轉化成位圖格式(Bitmap)的圖片返回。該方法在子線程中調用,代碼注釋如下:
static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale) {
...
CGImageRef imageRef = NULL;
//根據data創建dataProvider
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
//判斷報文格式是否是png或者jpg
if ([response.MIMEType isEqualToString:@"image/png"]) {
imageRef = CGImageCreateWithPNGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault); //創建png圖片
} else if ([response.MIMEType isEqualToString:@"image/jpeg"]) {
imageRef = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault); //創建jpg圖片
//如果存在png圖片或者jpg圖片
if (imageRef) {
//獲取圖片的顏色模型
CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(imageRef);
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(imageColorSpace);
//如果是CMYK模型,則無法轉成位圖,imageRef指針置為空
if (imageColorSpaceModel == kCGColorSpaceModelCMYK) {
CGImageRelease(imageRef);
imageRef = NULL;
}
}
}
CGDataProviderRelease(dataProvider);
//根據創建原格式圖片
UIImage *image = AFImageWithDataAtScale(data, scale);
if (!imageRef) { //如果imageRef為空,說明不是壓縮格式的圖片,或者無法進一步轉成Bitmap格式,直接返回原格式圖片
if (image.images || !image) {
return image;
}
imageRef = CGImageCreateCopy([image CGImage]);
if (!imageRef) {
return nil;
}
}
//根據imageRef獲取圖片相關信息
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
//如果圖片太大,直接返回原格式圖片
if (width * height > 1024 * 1024 || bitsPerComponent > 8) {
CGImageRelease(imageRef);
return image;
}
//獲取圖片相關信息
size_t bytesPerRow = 0;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
//如果是RGB顏色模型,根據像素是否包含alpha通道進行相應處理
if (colorSpaceModel == kCGColorSpaceModelRGB) {
uint32_t alpha = (bitmapInfo & kCGBitmapAlphaInfoMask);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wassign-enum"
if (alpha == kCGImageAlphaNone) {
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaNoneSkipFirst;
} else if (!(alpha == kCGImageAlphaNoneSkipFirst || alpha == kCGImageAlphaNoneSkipLast)) {
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaPremultipliedFirst;
}
#pragma clang diagnostic pop
}
//創建Bitmap的上下文
CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
CGColorSpaceRelease(colorSpace);
if (!context) {
CGImageRelease(imageRef);
return image;
}
//渲染到畫布上
CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, width, height), imageRef);
//獲取Bitmap格式的圖片
CGImageRef inflatedImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
//轉化為UIImage對象
UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation];
CGImageRelease(inflatedImageRef);
CGImageRelease(imageRef);
return inflatedImage;
}
該方法分為以下幾步:
首先根據報文數據創建圖像,如果content-type是PNG或者JPG格式,則imageRef指針指向該圖像。如果不是PNG或者JPG,或者顏色模型是CMYK,則不能進行后續轉化,imageRef指針置空。
調用AFImageWithDataAtScale方法直接生成原格式的UIImage對象,如果imageRef為空,說明不能進行后續轉化,直接返回原格式的圖片對象,結束方法。
-
如果imageRef不為空,說明可以進行轉化,通過imageRef獲取圖像相關信息,如下:
width Bitmap的寬度,單位為像素
height Bitmap的高度,單位為像素
bitsPerComponent 內存中像素的每個組件的位數. 例如,對于32位像素格式和RGB顏色模型,你應該將這個值設為8.
bytesPerRow Bitmap的每一行在內存所占的比特數
colorspace Bitmap上下文使用的顏色空間。
bitmapInfo Bitmap是否包含alpha通道
根據上述信息創建Bitmap的上下文,并渲染到畫布上。
調用CGBitmapContextCreateImage方法,通過上下文獲取Bitmap格式的CGImageRef對象,通過inflatedImageRef創建Bitmap格式的UIImage對象,返回該圖片。
結尾
本篇分析了報文數據反序列化的相關類,關于報文數據序列化和反序列化的分析到此告一段落。后續準備學習和分析AFNetworking的網絡通信主體部分。