AFNetworking 3.0 源碼閱讀筆記(四)

原文鏈接:http://itangqi.me/2016/05/13/the-notes-of-learning-afnetworking-four/

前言

通過前面的文章,我們已經知道 AFNetworking 是對 NSURLSession 的封裝,也瞭解它是如何發出請求的,在這裏我們對發出請求以及接收響應的過程進行序列化,這涉及到兩個模塊

前者是處理響應的模塊,將請求返回的數據解析成對應的格式。而後者的主要作用是修改請求(主要是 HTTP 請求)的頭部,提供了一些語義明確的接口設置 HTTP 頭部字段。

我們首先會對 AFURLResponseSerialization 進行簡單的介紹,因爲這個模塊使用在 AFURLSessionManager 也就是核心類中,而後者 AFURLRequestSerialization 主要用於 AFHTTPSessionManager 中,因爲它主要用於修改 HTTP 頭部


AFURLResponseSerialization

在瞭解模塊中類的具體實現之前,先看一下模塊的結構圖:

AFURLResponseSerialization 定義爲協議,且協議的內容非常簡單,只有一個必須實現的方法:

1
2
3
4
5
6
7
@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>

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

@end

遵循該協議的類同時也要遵循 NSObject、NSSecureCoding 和 NSCopying 這三個協議,以實現 Objective-C 對象的基本行爲、安全編碼以及拷貝。

  1. 模塊中的所有類都遵循 AFURLResponseSerialization 協議
  2. AFHTTPResponseSerializer 爲模塊中最終要的根類

AFHTTPResponseSerializer

下面我們對模塊中最重要的根類,也就是 AFHTTPResponseSerializer 的實現進行分析。它是在 AFURLResponseSerialization 模塊中最基本的類(因爲 AFURLResponseSerialization 只是一個協議)

初始化

首先,依然從實例化方法入手:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+ (instancetype)serializer {
    return [[self alloc] init];
}

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }

    self.stringEncoding = NSUTF8StringEncoding;

    self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
    self.acceptableContentTypes = nil;

    return self;
}

因爲是對 HTTP 響應進行序列化,所以這裏設置了 stringEncodingNSUTF8StringEncoding 而且沒有對接收的內容類型加以限制。

acceptableStatusCodes 設置爲從 200 到 299 之間的狀態碼, 因爲只有這些狀態碼錶示獲得了有效的響應

補充HTTP狀態碼

驗證響應的有效性

AFHTTPResponseSerializer 中方法的實現最長,並且最重要的就是 - [AFHTTPResponseSerializer validateResponse:data:error:]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
                    data:(NSData *)data
                   error:(NSError * __autoreleasing *)error
{
    BOOL responseIsValid = YES;
    NSError *validationError = nil;
    // 簡單的爲空判斷和類型判斷,注意如果 response 爲空或類型不對,反而 responseValid 爲 YES
    if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
        if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]]) {
truetruetrue#1: 返回內容類型無效
        }

        if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
truetruetrue#2: 返回狀態碼無效
        }
    }

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

    return responseIsValid;
}

這個方法根據在初始化方法中初始化的屬性 acceptableContentTypesacceptableStatusCodes 來判斷當前響應是否有效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;

其中第一、二部分的代碼非常相似,出現錯誤時通過 AFErrorWithUnderlyingError 生成格式化之後的錯誤,最後設置 responseIsValid

1
2
3
4
5
6
7
8
9
10
11
12
13
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;

第二部分的代碼講解略。

協議的實現

主要看 AFURLResponseSerialization 協議的實現:

1
2
3
4
5
6
7
8
- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    [self validateResponse:(NSHTTPURLResponse *)response data:data error:error];

    return data;
}

調用上面的方法對響應進行驗證,然後返回數據,並沒有複雜的邏輯。

AFJSONResponseSerializer

接下來,看一下 AFJSONResponseSerializer 這個繼承自 AFHTTPResponseSerializer 類的實現。

初始化方法只是在調用父類的初始化方法之後更新了 acceptableContentTypes 屬性:

1
2
3
4
5
6
7
8
9
10
- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }

    self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];

    return self;
}

協議的實現

這個類中與父類差別最大的就是對 AFURLResponseSerialization 協議的實現。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    #1: 驗證請求

    #2: 解決一個由只包含一個空格的響應引起的 bug, 略

    #3: 序列化 JSON
true
    #4: 移除 JSON 中的 null

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

    return responseObject;
}
  1. 驗證請求的有效性

    1
    2
    3
    4
    5
    
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
         if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
             return nil;
         }
     }
    
  2. 解決一個空格引起的 bug,見 https://github.com/rails/rails/issues/1742

  3. 序列化 JSON

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    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;
      }
    
  4. 移除 JSON 中的 null

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
    trueif (self.removesKeysWithNullValues && responseObject) {
    true    responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
    true}
    true``` 
    
    其中移除 JSON 中 null 的函數 `AFJSONObjectByRemovingKeysWithNullValues` 是一個遞歸調用的函數:
    
    ```objectivec
    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;
    }
    

其中移除 null 靠的就是 [mutableDictionary removeObjectForKey:key] 這一行代碼。

  • AFXMLParserResponseSerializerAFXMLDocumentResponseSerializerAFPropertyListResponseSerializer 、 AFImageResponseSerializer 及 AFCompoundResponseSerializer 將留給感興趣的同學。

參考

發佈了24 篇原創文章 · 獲贊 19 · 訪問量 34萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章