iOS源碼解析—AFNetworking(ResponseSerializer)

概述

上篇文章分析了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;
}

該方法分為以下幾步:

  1. 首先根據報文數據創建圖像,如果content-type是PNG或者JPG格式,則imageRef指針指向該圖像。如果不是PNG或者JPG,或者顏色模型是CMYK,則不能進行后續轉化,imageRef指針置空。

  2. 調用AFImageWithDataAtScale方法直接生成原格式的UIImage對象,如果imageRef為空,說明不能進行后續轉化,直接返回原格式的圖片對象,結束方法。

  3. 如果imageRef不為空,說明可以進行轉化,通過imageRef獲取圖像相關信息,如下:

    width Bitmap的寬度,單位為像素

    height Bitmap的高度,單位為像素

    bitsPerComponent 內存中像素的每個組件的位數. 例如,對于32位像素格式和RGB顏色模型,你應該將這個值設為8.

    bytesPerRow Bitmap的每一行在內存所占的比特數

    colorspace Bitmap上下文使用的顏色空間。

    bitmapInfo Bitmap是否包含alpha通道

  4. 根據上述信息創建Bitmap的上下文,并渲染到畫布上。

  5. 調用CGBitmapContextCreateImage方法,通過上下文獲取Bitmap格式的CGImageRef對象,通過inflatedImageRef創建Bitmap格式的UIImage對象,返回該圖片。

結尾

本篇分析了報文數據反序列化的相關類,關于報文數據序列化和反序列化的分析到此告一段落。后續準備學習和分析AFNetworking的網絡通信主體部分。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容