AFNetworking源碼探究(十一) —— 數(shù)據(jù)解析之子類中協(xié)議方法的實(shí)現(xiàn)(二)

版本記錄

版本號 時間
V1.0 2018.03.01

前言

我們做APP發(fā)起網(wǎng)絡(luò)請求,都離不開一個非常有用的框架AFNetworking,可以說這個框架的知名度已經(jīng)超過了蘋果的底層網(wǎng)絡(luò)請求部分,很多人可能不知道蘋果底層是如何發(fā)起網(wǎng)絡(luò)請求的,但是一定知道AFNetworking,接下來幾篇我們就一起詳細(xì)的解析一下這個框架。感興趣的可以看上面寫的幾篇。
1. AFNetworking源碼探究(一) —— 基本介紹
2. AFNetworking源碼探究(二) —— GET請求實(shí)現(xiàn)之NSURLSessionDataTask實(shí)例化(一)
3. AFNetworking源碼探究(三) —— GET請求實(shí)現(xiàn)之任務(wù)進(jìn)度設(shè)置和通知監(jiān)聽(一)
4. AFNetworking源碼探究(四) —— GET請求實(shí)現(xiàn)之代理轉(zhuǎn)發(fā)思想(一)
5. AFNetworking源碼探究(五) —— AFURLSessionManager中NSURLSessionDelegate詳細(xì)解析(一)
6. AFNetworking源碼探究(六) —— AFURLSessionManager中NSURLSessionTaskDelegate詳細(xì)解析(一)
7. AFNetworking源碼探究(七) —— AFURLSessionManager中NSURLSessionDataDelegate詳細(xì)解析(一)
8. AFNetworking源碼探究(八) —— AFURLSessionManager中NSURLSessionDownloadDelegate詳細(xì)解析(一)
9. AFNetworking源碼探究(九) —— AFURLSessionManagerTaskDelegate中三個轉(zhuǎn)發(fā)代理方法詳細(xì)解析(一)
10. AFNetworking源碼探究(十) —— 數(shù)據(jù)解析之?dāng)?shù)據(jù)解析架構(gòu)的分析(一)

回顧

上一篇我們主要介紹了有關(guān)數(shù)據(jù)解析類和協(xié)議,以及實(shí)現(xiàn)解析的架構(gòu),這一篇就分開講述各個類是如何實(shí)現(xiàn)對應(yīng)的數(shù)據(jù)解析的。


AFURLResponseSerialization協(xié)議

我們先看一下這個協(xié)議的接口

/**
 The `AFURLResponseSerialization` protocol is adopted by an object that decodes data into a more useful object representation, according to details in the server response. Response serializers may additionally perform validation on the incoming response and data.

 For example, a JSON response serializer may check for an acceptable status code (`2XX` range) and content type (`application/json`), decoding a valid JSON response into an object.
 */
@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>

/**
 The response object decoded from the data associated with a specified response.

 @param response The response to be processed.
 @param data The response data to be decoded.
 @param error The error that occurred while attempting to decode the response data.

 @return The object decoded from the specified response data.
 */
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

@end

根據(jù)服務(wù)器響應(yīng)中的細(xì)節(jié),AFURLResponseSerialization協(xié)議被一個對象采用,該對象將數(shù)據(jù)解碼為更有用的對象表示。 Response序列化器還可以對傳入響應(yīng)和數(shù)據(jù)執(zhí)行驗(yàn)證。例如,JSON響應(yīng)序列化器可以檢查可接受的狀態(tài)碼(2XX范圍)和內(nèi)容類型(application / json),將有效的JSON響應(yīng)解碼成對象


AFHTTPResponseSerializer

這個是所有其他解析類的父類,他遵守上面的AFURLResponseSerialization協(xié)議。

我們看一下協(xié)議在這個類中的實(shí)現(xiàn)

- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    [self validateResponse:(NSHTTPURLResponse *)response data:data error:error];

    return data;
}

這里調(diào)用了一個方法,進(jìn)行了指定response和數(shù)據(jù)的驗(yàn)證。

/**
 Validates the specified response and data.

 In its base implementation, this method checks for an acceptable status code and content type. Subclasses may wish to add other domain-specific checks.

 @param response The response to be validated.
 @param data The data associated with the response.
 @param error The error that occurred while attempting to validate the response.

 @return `YES` if the response is valid, otherwise `NO`.
 */
- (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response
                    data:(nullable NSData *)data
                   error:(NSError * _Nullable __autoreleasing *)error;

在其基本實(shí)現(xiàn)中,此方法檢查可接受的狀態(tài)碼和內(nèi)容類型。 子類可能希望添加其他域特定的檢查。

下面我們看一下驗(yàn)證過程,主要對應(yīng)下面這段代碼

- (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;
}

這是一個具有返回值類型為BOOL的方法,但是這里對于返回值并沒有使用。

(a) 最外層的判斷

最外層的判斷主要是

if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) 

就是如果response不是nil,并且response的類型是NSHTTPURLResponse。

(b) 第一個if判斷

在上面最外層判斷的內(nèi)部是兩個if判斷,根據(jù)不同的條件判斷數(shù)據(jù)是否有效以及在無效時應(yīng)該拋出怎樣的異常。

主要對應(yīng)下面這段代碼

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;
}

responseIsValid = NO,我們可以看出來,這一定是拋出異常,沒有驗(yàn)證通過的,但是為什么拋出異常呢?我們看一下。

如果有接受數(shù)據(jù)類型,如果不匹配response,而且響應(yīng)類型不為空,數(shù)據(jù)長度不為0。接著進(jìn)行判斷,如果數(shù)據(jù)長度大于0,而且有響應(yīng)URL,那么就生成mutableUserInfo信息,調(diào)用下面的方法生成錯誤信息。

static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
    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];
}

這里要注意,NSURLResponse中這個MIMEType屬性。

/*! 
    @abstract Returns the MIME type of the receiver.
    @discussion The MIME type is based on the information provided
    from an origin source. However, that value may be changed or
    corrected by a protocol implementation if it can be determined
    that the origin server or source reported the information
    incorrectly or imprecisely. An attempt to guess the MIME type may
    be made if the origin source did not report any such information.
    @result The MIME type of the receiver.

     @abstract返回接收者的MIME類型。
     @討論MIME類型基于提供的信息
     來源。 但是,該值可能會改變或
     如果可以確定原始服務(wù)器或來源報告了信息
     不正確或不準(zhǔn)確,則由協(xié)議實(shí)施糾正
     。如果原始資料來源未報告任何此類信息, 
     可以嘗試猜測MIME類型
     @result接收者的MIME類型。
*/
@property (nullable, readonly, copy) NSString *MIMEType;

(c) 第二個if判斷

主要對應(yīng)下邊這段代碼

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;
}

判斷自己可接受的狀態(tài)碼,如果和response的狀態(tài)碼不匹配,則進(jìn)入if塊,生成錯誤和標(biāo)識。

(d) error和responseIsValid判斷

主要是下面一段代碼

if (error && !responseIsValid) {
    *error = validationError;
}

這里,如果error不為空,并且responseIsValid == NO,也就是說上面兩個if判斷至少走過了一個,這時候給error進(jìn)行了賦值。

*error = validationError;

這個方法就是來判斷返回數(shù)據(jù)與咱們使用的解析器是否匹配,需要解析的狀態(tài)碼是否匹配。

兩個屬性值,一個acceptableContentTypes,一個acceptableStatusCodes,兩者在初始化的時候有給默認(rèn)值,如果給acceptableContentTypes定義了不匹配的類型,那么數(shù)據(jù)仍舊會解析錯誤。


AFJSONResponseSerializer

AFJSONResponseSerializerAFHTTPResponseSerializer的一個子類,用于驗(yàn)證和解碼JSON響應(yīng)。

默認(rèn)情況下,AFJSONResponseSerializer接受以下MIME類型,其中包括官方標(biāo)準(zhǔn),application / json以及其他常用類型:

  • application / json
  • text / json
  • text / javascript

我們看一下協(xié)議在這個類中的實(shí)現(xiàn)

- (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;
    // 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
    BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
    if (data.length > 0 && !isSpace) {
        responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
    } else {
        return nil;
    }

    if (self.removesKeysWithNullValues && responseObject) {
        responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
    }

    if (error) {
        *error = AFErrorWithUnderlyingError(serializationError, *error);
    }

    return responseObject;
}

下面就看一下,這里都做了a什么

(a) 有效性的驗(yàn)證

我們看一下如何進(jìn)行有效性的驗(yàn)證的。

if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
    if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
        return nil;
    }
}

就是調(diào)用我們上面解析的,驗(yàn)證有效性的方法。如果無效,進(jìn)入判斷,接著if判斷,如果error為空,或者有錯誤,去函數(shù)里判斷。

static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
    if ([error.domain isEqualToString:domain] && error.code == code) {
        return YES;
    } else if (error.userInfo[NSUnderlyingErrorKey]) {
        return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
    }

    return NO;
}

數(shù)據(jù)無效后,返回nil。

(b) 幾個條件判斷

下面就是幾個條件判斷,滿足的話直接序列化對應(yīng)的JSON數(shù)據(jù),不滿足的話返回nil。

id responseObject = nil;
NSError *serializationError = 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
BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
if (data.length > 0 && !isSpace) {
    responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
} else {
    return nil;
}

if (self.removesKeysWithNullValues && responseObject) {
    responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}

if (error) {
    *error = AFErrorWithUnderlyingError(serializationError, *error);
}
  • 第一組條件判斷
BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
if (data.length > 0 && !isSpace) {
    responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
} else {
    return nil;
}

這里首先判斷數(shù)據(jù)是否為空,利用isEqualToData:方法進(jìn)行判斷,如果不為空,并且數(shù)據(jù)長度大于0,那么就進(jìn)行JSON數(shù)據(jù)的序列化。

responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];

如果不滿足上面條件就返nil。

  • 第二組條件判斷
if (self.removesKeysWithNullValues && responseObject) {
    responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}

這里有一個屬性

/**
 Whether to remove keys with `NSNull` values from response JSON. Defaults to `NO`.
 */
@property (nonatomic, assign) BOOL removesKeysWithNullValues;

是否從響應(yīng)JSON中刪除具有NSNull值的鍵。 默認(rèn)為NO。如果需要移除這個鍵并且上面的responseObject已經(jīng)序列化成功,那么就要調(diào)用下面的函數(shù)移除具有NSNull值的鍵。

static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
    if ([JSONObject isKindOfClass:[NSArray class]]) {
        NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
        for (id value in (NSArray *)JSONObject) {
            [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
        }

        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;
}

這里有一個屬性和枚舉,一起來看一下

/**
 Options for reading the response JSON data and creating the Foundation objects. For possible values, see the `NSJSONSerialization` documentation section "NSJSONReadingOptions". `0` by default.
 */
用于讀取響應(yīng)JSON數(shù)據(jù)并創(chuàng)建Foundation對象的選項(xiàng)。 有關(guān)可能的值,請參閱“NSJSONSerialization”文檔部分“NSJSONReadingOptions”。 默認(rèn)為'0'

@property (nonatomic, assign) NSJSONReadingOptions readingOptions;

typedef NS_OPTIONS(NSUInteger, NSJSONReadingOptions) {
    NSJSONReadingMutableContainers = (1UL << 0),
    NSJSONReadingMutableLeaves = (1UL << 1),
    NSJSONReadingAllowFragments = (1UL << 2)
} API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
  • 第三組條件判斷
if (error) {
    *error = AFErrorWithUnderlyingError(serializationError, *error);
}

如果error不為空,那么就利用函數(shù)AFErrorWithUnderlyingError生成NSError對象并賦值。

后記

本篇講述了一個AFURLResponseSerialization協(xié)議以及AFHTTPResponseSerializerAFJSONResponseSerializer類中父類那個協(xié)議方法的實(shí)現(xiàn)。

最后編輯于
?著作權(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)容