AFNetworking框架分析(五)——響應的序列化AFURLResponseSerialization分析

這一篇將分析網(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è)面建議不要直接使用父類。

父類AFHTTPResponseSerializer初始化方法

所以,當需要響應具體不同類型的數(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 博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,545評論 19 139
  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地數(shù)據(jù)庫組件 SD...
    陽明AI閱讀 16,201評論 3 119
  • AF中對接收響應的過程進行序列化,這涉及到AFURLResponseSerialization模塊。將請求返回的數(shù)...
    WeiHing閱讀 1,369評論 0 1
  • Spring IOC體系結(jié)構(gòu) (1) BeanFactory Spring Bean的創(chuàng)建是典型的工廠模式,這一系...
    記住時光閱讀 449評論 0 0
  • 因為你 我愿意 遇見愛情
    晚開的花兒_a564閱讀 225評論 0 0

友情鏈接更多精彩內(nèi)容