這一篇將分析網(wǎng)絡(luò)請求收到數(shù)據(jù)時的響應AFURLResponseSerialization序列化過程。
當AFURLRequestSerialization類將所有的請求數(shù)據(jù)處理完成發(fā)送請求之后,當收到返回的數(shù)據(jù)信息時,這時就要靠AFURLResponseSerialization類來完成不同類型返回數(shù)據(jù)的序列化操作。
從AFURLResponseSerialization頭文件中,可以看出與AFURLRequestSerialization類的結(jié)構(gòu)非常相似。從上往下,首先聲明了AFURLResponseSerialization協(xié)議,協(xié)議中只有一個方法,將response解碼成指定的相關(guān)數(shù)據(jù),這是所有響應類都需要遵循的協(xié)議。之后聲明了一個AFHTTPResponseSerializer類,作為響應類的根類。再往下的類,都是繼承自AFHTTPResponseSerializer的子類,分別是AFJSONResponseSerializer(JSON格式數(shù)據(jù)響應,默認)、AFXMLParserResponseSerializer(iOS端XML數(shù)據(jù)解析響應)、AFXMLDocumentResponseSerializer(MAC OS端XML數(shù)據(jù)解析響應)、AFPropertyListResponseSerializer(PList格式數(shù)據(jù)解析響應)、AFImageResponseSerializer(圖片數(shù)據(jù)解析響應)和AFCompoundResponseSerializer(復合式數(shù)據(jù)解析響應)
在父類AFHTTPResponseSerializer中,遵循的協(xié)議方法不做任何事情 只做一次response的驗證。實現(xiàn)方法中,只有[self validateResponse:(NSHTTPURLResponse *)response data:data error:error]驗證response是否合規(guī)的方法。而且初始化init方法中,父類只是設(shè)置編碼格式為UTF-8,設(shè)置http狀態(tài)碼為200-299,表示只有這些狀態(tài)碼獲得了有效的響應,而不在接受范圍內(nèi)的狀態(tài)碼和內(nèi)容類型會在數(shù)據(jù)解析時發(fā)生錯誤。而且其中一句代碼self.acceptableContentTypes = nil;,本身acceptableContentTypes用于設(shè)置可接受的contentType,這里置為nil,也從側(cè)面建議不要直接使用父類。

所以,當需要響應具體不同類型的數(shù)據(jù)序列化操作時,都是由其對應的子類來完成任務。
- (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]] &&
!([response MIMEType] == nil && [data length] == 0)) {
if ([data length] > 0 && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
}
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
}
responseIsValid = NO;
}
if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
}
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
responseIsValid = NO;
}
}
if (error && !responseIsValid) {
*error = validationError;
}
return responseIsValid;
}
該驗證方法中,默認設(shè)置返回BOOL值為YES,后續(xù)只處理數(shù)據(jù)無效的各種情況。首先,根據(jù)初始化的acceptableContentTypes 判斷MIME媒體類型是否合法;其次,根據(jù)初始化的acceptableStatusCodes 判斷狀態(tài)碼是否有效。
代碼實現(xiàn)中,NSLocalizedDescriptionKey是NSError頭文件中預定義的鍵,標識錯誤的本地化描述.可以通過NSError的localizedDescription方法獲得對應的值信息,而NSURLErrorFailingURLErrorKey相應的值是包含導致加載失敗的URL的NSURL。生成錯誤信息字典,會返回unacceptable content-type的信息,并將錯誤信息記錄在了mutableUserInfo中。
因此,如果content-type不滿足,那么產(chǎn)生的validationError就是Domain為AFURLResponseSerializationErrorDomain,code為NSURLErrorCannotDecodeContentData;如果MIME type不滿足,那么產(chǎn)生的validationError就是Domain為AFURLResponseSerializationErrorDomain,code為NSURLErrorBadServerResponse。
這里需要注意一個error處理邏輯,不管是判斷媒體類型還是狀態(tài)碼,都用到了validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);函數(shù)。查看此函數(shù)的實現(xiàn),可以發(fā)現(xiàn)AFN已經(jīng)處理好了當兩種錯誤同時出現(xiàn)的情況以及優(yōu)先級顯示。將媒體類型的error信息放入至狀態(tài)碼error中userInfo字典的NSUnderlyingErrorKey值中去。
// 設(shè)置一個underlyingError為error的附屬
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
// 是否傳入了error
if (!error) {
return underlyingError;
}
// 是否已經(jīng)有附屬
if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
return error;
}
// 取出error的userInfo
NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;
return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}
以JSON格式的數(shù)據(jù)為例,當響應到JSON格式的數(shù)據(jù)時,就需要AFJSONResponseSerializer子類去完成response序列化工作。
首先在初始化方法init中,設(shè)置了acceptableContentTypes的集合內(nèi)容self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];(小提示:json格式是 js 代碼的一個子集。也就是說 json 格式的數(shù)據(jù),也是 js 代碼,也會被瀏覽器的js引擎執(zhí)行,從而生成 json 對象)
接下來,AFJSONResponseSerializer類遵循的協(xié)議方法會對JSON格式的數(shù)據(jù)進行刪除空數(shù)據(jù)處理,利用遍歷與遞歸將value值為空的key進行刪除操作。類似的,AFXMLParserResponseSerializer、AFXMLDocumentResponseSerializer、AFPropertyListResponseSerializer都是將返回數(shù)據(jù)進行對應格式類型的數(shù)據(jù)轉(zhuǎn)換,并刪除其中無效的key,最終返回出response。AFCompoundResponseSerializer類型的,會進行所有支持數(shù)據(jù)類型的遍歷,以匹配哪種類型的數(shù)據(jù)可以進行數(shù)據(jù)解析。
這里單獨拿出AFImageResponseSerializer類進行分析,此類用于接收處理圖片類型的數(shù)據(jù)。在解析圖片數(shù)據(jù)時,用到了函數(shù)static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale)來手動解壓圖片。根據(jù)response和scale(scale大小為屏幕寬高)轉(zhuǎn)換為位圖bitmap,這里因為解碼的過程都是在網(wǎng)絡(luò)請求回來調(diào)用的,避免了系統(tǒng)將在主線程進行解碼,從而顯示圖片的時候直接繪制,節(jié)省GPU開銷。此函數(shù)中主要涉及到了CoreGraphics內(nèi)容。
對CoreGraphics有興趣了解的,可以看下阿里云對其介紹鏈接在此
首先將圖片data封裝至CGDataProviderRef對象中,然后只針對jpg與png格式的圖片數(shù)據(jù)來單獨給CGImageRef對象賦值,以此來創(chuàng)建CGImage用于表示data中的圖片是壓縮格式。在jpg格式中,AFN單獨判斷了CMKY類型的圖片不支持轉(zhuǎn)換為位圖
// CGImageCreateWithJPEGDataProvider does not properly handle CMKY, so fall back to AFImageWithDataAtScale
if (imageColorSpaceModel == kCGColorSpaceModelCMYK) {
CGImageRelease(imageRef);
imageRef = NULL;
}
接下來,根據(jù)圖片data數(shù)據(jù)創(chuàng)建一個UIImage對象,然后根據(jù)上面的CGImageRef對象判斷是否為壓縮格式圖片。若非壓縮格式圖片且不為空,則直接把原圖片返回出去,為空時直接返回nil。接下來就到了處理壓縮格式的圖片流程
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;
}
// CGImageGetBytesPerRow() calculates incorrectly in iOS 5.0, so defer to CGBitmapContextCreate
size_t bytesPerRow = 0;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
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
}
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);
CGImageRef inflatedImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation];
CGImageRelease(inflatedImageRef);
CGImageRelease(imageRef);
return inflatedImage;
按照個人理解,首先是通過CoreGraphics框架來獲取圖片CGImageRef對象的寬高、以及每個顏色的比特數(shù)。當寬高像素大于1024*1024像素,或者每個顏色的比特數(shù)大于8時,表明圖片過大直接返回原圖出去。接下來,獲取到圖片的各種信息,來創(chuàng)建一個CGContextRef類型對象context,也就是bitmap的上下文。繼續(xù)把context渲染到畫布上,根據(jù)context生成一個bitmap格式的圖片。然后將圖片轉(zhuǎn)換成UIImage格式的圖片作為response數(shù)據(jù)返回給AFURLSessionManager類。最終通過block返回出圖片數(shù)據(jù)。
小插曲:Apple官方更推薦使用png格式的壓縮圖片進行網(wǎng)絡(luò)傳輸返回至手機端。pngcrush工具,了解一下,可以更快速地解壓與渲染圖片,節(jié)省系統(tǒng)資源。
該文章首次發(fā)表在 簡書:我只不過是出來寫寫代碼 博客,并自動同步至 騰訊云:我只不過是出來寫寫iOS 博客