AFNetworking 3.0 源碼分析-AFURLResponseSerialization

AF中對接收響應(yīng)的過程進(jìn)行序列化,這涉及到AFURLResponseSerialization模塊。將請求返回的數(shù)據(jù)解析成對應(yīng)的格式。而這個模塊使用在 AFURLSessionManager
也就是核心類中。

1.模塊結(jié)構(gòu)

協(xié)議:

@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>

該協(xié)議只有一個必須實(shí)現(xiàn)的方法

- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error

根類:
AFHTTPResponseSerializer遵循AFURLResponseSerialization協(xié)議。

子類:
AFJSONResponseSerializer、AFXMLParserResponseSerializer、AFXMLDocumentResponseSerializerAFPropertyListResponseSerializer、AFImageResponseSerializerAFCompoundResponseSerializer,即所有子類都遵循AFURLResponseSerialization協(xié)議。

2.AFHTTPResponseSerializer

這是這個模塊中最基本的類。

  • 初始化
 + (instancetype)serializer {
    return [[self alloc] init];
}

 - (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }
    self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];//200-299的HTTP狀態(tài)碼。NSIndexSet是一個有序的,唯一的,無符號整數(shù)的集合。
    self.acceptableContentTypes = nil;//沒有對接收的內(nèi)容類型加以限制
    return self;
}

屬性acceptableContentTypesacceptableStatusCodes是在初始化時給定默認(rèn)值的,我們也可以自己去定義。不在接受范圍內(nèi)的狀態(tài)碼和內(nèi)容類型會在數(shù)據(jù)解析時發(fā)生錯誤。
設(shè)置了可接受的http status code是200-299,因?yàn)橹挥羞@些狀態(tài)碼表示獲得了有效的響應(yīng)。
另外,沒有對可接受的MIME type進(jìn)行設(shè)置(交給子類來做)。在老版本的AF中,還有stringEncoding,規(guī)定utf8數(shù)據(jù)格式。

  • 驗(yàn)證響應(yīng)和數(shù)據(jù)的有效性
//驗(yàn)證響應(yīng)和數(shù)據(jù)的有效性(驗(yàn)證MIMEType和status code)。子類可添加其他特定域的檢查。
 - (BOOL)validateResponse:(NSHTTPURLResponse *)response
                    data:(NSData *)data
                   error:(NSError * __autoreleasing *)error
{
    BOOL responseIsValid = YES;//response是否合法
    NSError *validationError = nil;
    //response是否存在和類型判斷,如果response為空或者不是NSHTTPURLResponse類型,responseIsValid=YES!
    if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
        //根據(jù)在初始化方法中初始化的屬性 acceptableContentTypes 和 acceptableStatusCodes 來判斷當(dāng)前響應(yīng)是否有效
        //1.response的內(nèi)容類型不對(MIMEType)
        if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
            !([response MIMEType] == nil && [data length] == 0)) {
            //數(shù)據(jù)解析失敗
            if ([data length] > 0 && [response URL]) {
                //NSLocalizedDescriptionKey是NSError頭文件中預(yù)定義的鍵,標(biāo)識錯誤的本地化描述.可以通過NSError的localizedDescription方法獲得對應(yīng)的值信息
                //NSURLErrorFailingURLErrorKey相應(yīng)的值是包含導(dǎo)致加載失敗的URL的NSURL。 此鍵僅存在于NSURLErrorDomain中。
                //生成錯誤信息字典。會返回unacceptable content-type的信息,并將錯誤信息記錄在了mutableUserInfo中
                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;
                }
                //+errorWithDomain: code: userInfo:創(chuàng)建和初始化NSError對象
                //NSErrorDomain錯誤域 - 這可以是預(yù)定義的NSError域之一,也可以是描述自定義域的任意字符串。 域名不能為空。
                //收到的內(nèi)容數(shù)據(jù)具有未知內(nèi)容編碼(解析數(shù)據(jù)出錯)。NSURLErrorCannotDecodeContentData = -1016,NSError錯誤碼
                //出現(xiàn)錯誤時通過AFErrorWithUnderlyingError函數(shù)生成本地格式化的錯誤
                validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
            }

            responseIsValid = NO;
        }
        //2.狀態(tài)碼無效
        if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
            //-localizedStringForStatusCode:根據(jù)狀態(tài)碼獲取本地化文本內(nèi)容
            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;
            }
            //收到從服務(wù)器來的錯誤數(shù)據(jù) NSURLErrorBadServerResponse = -1011,NSError錯誤碼
            validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
            responseIsValid = NO;
        }
    }
    if (error && !responseIsValid) {
        *error = validationError;
    }
    return responseIsValid;
}
/*
 1.如果content-type不滿足,那么產(chǎn)生的validationError就是Domain為AFURLResponseSerializationErrorDomain,code為NSURLErrorCannotDecodeContentData。
如果MIME type不滿足,,那么產(chǎn)生的validationError就是Domain為AFURLResponseSerializationErrorDomain,code為NSURLErrorBadServerResponse。
 2.方法中,有可能會出現(xiàn)兩個錯誤,在self.acceptableContentTypes和self.acceptableStatusCodes這兩個判斷中,如果都出現(xiàn)錯誤怎么辦呢?
 這就用到了NSUnderlyingErrorKey 這個字段,它表示一個優(yōu)先的錯誤,value為NSError對象。
 */

1.根據(jù)初始化中的屬性acceptableContentTypesacceptableStatusCodes判斷響應(yīng)是否有效。

2.content-type不對,返回unacceptable content-type的信息,并將錯誤信息記錄在了mutableUserInfo中。MIME type不對,處理相似,這里不展開。
這個記錄了錯誤信息的字典,系統(tǒng)提供的KEY值:



不過也可以自定義KEY值,比如AF中就自定義了AFNetworkingOperationFailingURLResponseErrorKeyAFNetworkingOperationFailingURLResponseDataErrorKey。

3.出現(xiàn)錯誤時通過AFErrorWithUnderlyingError函數(shù)生成本地格式化的錯誤。
3.1.如果content-type不滿足,那么產(chǎn)生的validationError就是Domain為AFURLResponseSerializationErrorDomain,code為NSURLErrorCannotDecodeContentData的自定義NSError。
3.2.如果MIME type不滿足,那么產(chǎn)生的validationError就是Domain為AFURLResponseSerializationErrorDomain,code為NSURLErrorBadServerResponse的自定義NSError。
關(guān)于NSError:NSError詳解 NSError錯誤code對照表 自定義NSError

4.如果content type和MIMEtype同時出錯,這就用到了NSUnderlyingErrorKey這個字段,它表示一個優(yōu)先的錯誤,value為NSError對象。
具體看下面這個函數(shù):

//生成本地格式化的錯誤。填充錯誤信息,一些處理過程中產(chǎn)生的錯誤信息填充到我們需要返回給用戶的自定義錯誤中
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
    //NSUnderlyingErrorKey表示優(yōu)先錯誤
    if (!error) {
        return underlyingError;
    }

    if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
        return error;
    }

    NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
    mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;

    return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}

在兩者都出錯的情況下,那么UnderlyingError就是content type error。

  • 協(xié)議的實(shí)現(xiàn)
    1.AFURLResponseSerialization協(xié)議:
//從與指定響應(yīng)相關(guān)聯(lián)的數(shù)據(jù)中decode得到的響應(yīng)對象。
 - (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error{
    //調(diào)用驗(yàn)證方法,返回data
    [self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
    return data;
}

把驗(yàn)證方法調(diào)用完之后就返回data,沒有其他實(shí)現(xiàn)了。

2.NSSecureCoding、NSCopying協(xié)議:一些歸檔和自定義copy的方法,比較常規(guī)的寫法,不做說明了。

3.AFJSONResponseSerializer

可接受的數(shù)據(jù)類型:application/json,text/json,text/javascript。

  • 協(xié)議的實(shí)現(xiàn)
 - (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    //驗(yàn)證MIMEType和status code
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
        //error為空或者 錯誤、優(yōu)先錯誤匹配error code和domain(在這里是content type類型的錯誤)
        if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
            return nil;
        }
    }

    // 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.
    // See https://github.com/rails/rails/issues/1742
    //數(shù)據(jù)是否是一個空格
    BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
    //如果數(shù)據(jù)為空或者是空格,就不json解析
    if (data.length == 0 || isSpace) {
        return nil;
    }
    
    NSError *serializationError = nil;
    //json解析。NSJSON只支持解析UTF8編碼的數(shù)據(jù)
    id responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];

    if (!responseObject)
    {
        if (error) {
            //用json解析的error去填充錯誤信息
            *error = AFErrorWithUnderlyingError(serializationError, *error);
        }
        return nil;
    }
    //是否要從響應(yīng)的JSON數(shù)據(jù)中刪除帶有“NSNull”值的鍵
    if (self.removesKeysWithNullValues) {
        return AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
    }

    return responseObject;
}

1.驗(yàn)證響應(yīng)
驗(yàn)證失敗。在沒有error 或者 錯誤中的code是NSURLErrorCannotDecodeContentData(即content type不匹配)的情況下,是不能解析數(shù)據(jù)的,就返回nil。用到的函數(shù):

// 檢測錯誤或者優(yōu)先錯誤中是否匹配code和domain
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
    //判斷錯誤域和傳過來的域名是否一致,錯誤code是否一致
    if ([error.domain isEqualToString:domain] && error.code == code) {
        return YES;
    } else if (error.userInfo[NSUnderlyingErrorKey]) {//如果NSUnderlyingErrorKey對應(yīng)有值,就再進(jìn)行判斷
        return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
    }

    return NO;
}

2.處理返回的數(shù)據(jù)中只有空格的情況
如果數(shù)據(jù)為空或者只有一個空格,就不解析。

3.解析JSON
readingOptions屬性設(shè)置json的讀取選項(xiàng)。這里的默認(rèn)值是NSJSONReadingMutableContainers

typedef NS_OPTIONS(NSUInteger, NSJSONReadingOptions) {
    //返回可變?nèi)萜?,NSMutableDictionary或NSMutableArray
    NSJSONReadingMutableContainers = (1UL << 0), 
    //返回的JSON對象中字符串的值為NSMutableString
    NSJSONReadingMutableLeaves = (1UL << 1),
    //允許JSON字符串最外層既不是NSArray也不是NSDictionary,但必須是有效的JSON Fragment。例如使用這個選項(xiàng)可以解析 @“123” 這樣的字符串。
    NSJSONReadingAllowFragments = (1UL << 2)
} NS_ENUM_AVAILABLE(10_7, 5_0);

傳送門- JSON解析 NSJSONReadingMutableContainers的作用

4.是否要從響應(yīng)的JSON數(shù)據(jù)中刪除帶有“NSNull”值的鍵
用到的函數(shù):主要通過遞歸的手段來實(shí)現(xiàn)的。

//從響應(yīng)的JSON數(shù)據(jù)中刪除帶有“NSNull”值的鍵
static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
    //數(shù)組
    if ([JSONObject isKindOfClass:[NSArray class]]) {
        NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
        //遍歷數(shù)組,通過遞歸的手段清空數(shù)組內(nèi)的null
        for (id value in (NSArray *)JSONObject) {
            [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
        }
        //按位與操作,解析類型是否NSJSONReadingMutableContainers(mutableArray或者mutabledictionary)
        return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
    }
    //字典
    else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
        NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
        for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
            id value = (NSDictionary *)JSONObject[key];
            if (!value || [value isEqual:[NSNull null]]) {
                [mutableDictionary removeObjectForKey:key];
            } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
                mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
            }
        }

        return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
    }

    return JSONObject;
}

4.AFXMLParserResponseSerializer、AFXMLDocumentResponseSerializer、AFPropertyListResponseSerializer

AFXMLParserResponseSerializer用來解析XML數(shù)據(jù),支持的ContentType:application/xml、text/xml。
AFXMLDocumentResponseSerializer同上,但這個類只能在mac os x上使用。
AFPropertyListResponseSerializer用來解析plist數(shù)據(jù),支持的ContentType:application/x-plist。
這三個子類的實(shí)現(xiàn)和上面JSON子類的實(shí)現(xiàn)差不多,就不具體展開了。

5.AFImageResponseSerializer

用于驗(yàn)證和解碼圖像響應(yīng)。

- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    //驗(yàn)證MIME type和status code
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
        if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
            return nil;
        }
    }
    //圖片解壓。宏判斷是那種設(shè)備,進(jìn)行對應(yīng)的圖片解壓處理
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
    if (self.automaticallyInflatesResponseImage) {//是否對響應(yīng)的圖片進(jìn)行自動處理
        return AFInflatedImageFromResponseWithDataAtScale((NSHTTPURLResponse *)response, data, self.imageScale);
    } else {
        return AFImageWithDataAtScale(data, self.imageScale);
    }
#else
    // Ensure that the image is set to it's correct pixel width and height
    NSBitmapImageRep *bitimage = [[NSBitmapImageRep alloc] initWithData:data];
    NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize([bitimage pixelsWide], [bitimage pixelsHigh])];
    [image addRepresentation:bitimage];

    return image;
#endif

    return nil;
}

在這里用到了幾個方法,寫在了UIImage分類UIImage (AFNetworkingSafeImageLoading)里面。下面來看一下分類中的這幾個方法:
1.把NSData安全地轉(zhuǎn)換為UIImage。

+ (UIImage *)af_safeImageWithData:(NSData *)data {
    UIImage* image = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        imageLock = [[NSLock alloc] init];
    });
    
    [imageLock lock];//上鎖
    image = [UIImage imageWithData:data];
    [imageLock unlock];//開鎖
    return image;
}

當(dāng)我們讀寫一個數(shù)據(jù)的時候,由于數(shù)據(jù)還可能被別人讀寫,這就有可能出現(xiàn)不安全的情況,為了解決這個問題,就使用了“鎖”。如果對線程鎖比較熟悉的話就容易理解了,簡單說呢,就是在寫數(shù)據(jù)前先上鎖,那么別人就無法使用這塊數(shù)據(jù)了,直到你執(zhí)行完數(shù)據(jù)操作、解鎖。

2.私有函數(shù),按照scale對圖片進(jìn)行伸縮處理

//返回一個按照scale收縮的圖片
static UIImage * AFImageWithDataAtScale(NSData *data, CGFloat scale) {
    UIImage *image = [UIImage af_safeImageWithData:data];
    if (image.images) {//gif圖不需要伸縮
        return image;
    }    
    return [[UIImage alloc] initWithCGImage:[image CGImage] scale:scale orientation:image.imageOrientation];
}

關(guān)于image.images:這個屬性第一次接觸,大致看了一下,常見的應(yīng)用是用來生成一個gif效果。在gif圖中表示這個Gif包含了多少張圖片。其余用法還沒有深入研究。

3.根據(jù)響應(yīng)結(jié)果和scale返回一張圖片.完成圖像解壓工作

static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale)

這個函數(shù)實(shí)現(xiàn)很長,用到了CoreGraphics上的一些東西,主要完成iOS、TV、Watch設(shè)備下的圖像解壓工作。關(guān)于圖像解壓的目的,我在這篇文章中讀到這么一段話:

當(dāng)我們調(diào)用UIImage的方法imageWithData:方法把數(shù)據(jù)轉(zhuǎn)成UIImage對象后,其實(shí)這時UIImage對象還沒準(zhǔn)備好需要渲染到屏幕的數(shù)據(jù),現(xiàn)在的網(wǎng)絡(luò)圖像PNG和JPG都是壓縮格式,需要把它們解壓轉(zhuǎn)成bitmap后才能渲染到屏幕上,如果不做任何處理,當(dāng)你把UIImage賦給UIImageView,在渲染之前底層會判斷到UIImage對象未解壓,沒有bitmap數(shù)據(jù),這時會在主線程對圖片進(jìn)行解壓操作,再渲染到屏幕上。這個解壓操作是比較耗時的,如果任由它在主線程做,可能會導(dǎo)致速度慢UI卡頓的問題。
AFImageResponseSerializer除了把返回數(shù)據(jù)解析成UIImage外,還會把圖像數(shù)據(jù)解壓,這個處理是在子線程(AFNetworking專用的一條線程,詳見AFURLConnectionOperation),處理后上層使用返回的UIImage在主線程渲染時就不需要做解壓這步操作,主線程減輕了負(fù)擔(dān),減少了UI卡頓問題。
具體實(shí)現(xiàn)上在AFInflatedImageFromResponseWithDataAtScale里,創(chuàng)建一個畫布,把UIImage畫在畫布上,再把這個畫布保存成UIImage返回給上層。只有JPG和PNG才會嘗試去做解壓操作,期間如果解壓失敗,或者遇到CMKY顏色格式的jpg,或者圖像太大(解壓后的bitmap太占內(nèi)存,一個像素3-4字節(jié),搞不好內(nèi)存就爆掉了),就直接返回未解壓的圖像。
另外在代碼里看到iOS才需要這樣手動解壓,MacOS上已經(jīng)有封裝好的對象NSBitmapImageRep可以做這個事。

6.AFCompoundResponseSerializer

這是一個對復(fù)合類型的響應(yīng)進(jìn)行處理的子類。

- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    //遍歷數(shù)組,只要是屬于AFHTTPResponseSerializer及其子類的類型,就執(zhí)行相應(yīng)的響應(yīng)操作。
    for (id <AFURLResponseSerialization> serializer in self.responseSerializers) {
        if (![serializer isKindOfClass:[AFHTTPResponseSerializer class]]) {
            continue;
        }

        NSError *serializerError = nil;
        id responseObject = [serializer responseObjectForResponse:response data:data error:&serializerError];
        if (responseObject) {
            if (error) {
                *error = AFErrorWithUnderlyingError(serializerError, *error);
            }

            return responseObject;
        }
    }
    //以上類型都不是,就執(zhí)行默認(rèn)響應(yīng)操作。
    return [super responseObjectForResponse:response data:data error:error];//調(diào)用父類方法
}

responseSerializers屬性,這個數(shù)組中裝著多種序列化類型,比如上面講到的JSON、XML等等。
遍歷數(shù)組,只要是屬于AFHTTPResponseSerializer及其子類的類型,就執(zhí)行相應(yīng)的響應(yīng)操作。如果以上類型都不是,就執(zhí)行默認(rèn)的響應(yīng)。

詳細(xì)源碼注釋請戳:https://github.com/huixinHu/AFNetworking-

參考文章
AFNetworking源碼閱讀(五)
AFNetworking2.0源碼解析AFURLResponseSerialization

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

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

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