AFNetworking 3.0 源码阅读笔记(四)

前言

通过前面的文章,我们已经知道 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 将留给感兴趣的同学。

参考