AFNetworking3源码阅读-Serialization

Serialization 包括 AFURLResponseSerializationAFURLRequestSerialization

AFURLResponseSerialization 是处理响应的模块,将请求返回的数据解析成对应的格式。而AFURLRequestSerialization的主要作用是修改请求(主要是 HTTP 请求)的头部,提供了一些语义明确的接口设置 HTTP 头部字段。

AFURLResponseSerialization

AFURLResponseSerialization 是一个协议,协议的内容为:

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

它的实现类为:

  • AFURLResponseSerialization
    • AFHTTPResponseSerializer
      • AFJSONResponseSerializer
      • AFXMLParserResponseSerializer
      • AFXMLDocumentResponseSerializer
      • AFPropertyListResponseSerializer
      • AFImageResponseSerializer
      • AFCompoundResponseSerializer

在文件中 所有类都遵循AFURLResponseSerialization,而AFHTTPResponseSerializer是其他类的基类。

AFHTTPResponseSerializer

初始化:

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

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

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

return self;
}

因为是对 HTTP 响应进行序列化,所以这里设置了 stringEncoding 为 NSUTF8StringEncoding 而且没有对接收的内容类型加以限制。
将 acceptableStatusCodes 设置为从 200 到 299 之间的状态码, 因为只有这些状态码表示获得了有效的响应。

验证响应的有效性:

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;

if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]]) {
#1: 返回内容类型无效
}

if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
#2: 返回状态码无效
}
}

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

return responseIsValid;
}

这个方法根据在初始化方法中初始化的属性 acceptableContentTypes 和 acceptableStatusCodes 来判断当前响应是否有效。

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

初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+ (instancetype)serializer {
return [self serializerWithReadingOptions:(NSJSONReadingOptions)0];
}

+ (instancetype)serializerWithReadingOptions:(NSJSONReadingOptions)readingOptions {
AFJSONResponseSerializer *serializer = [[self alloc] init];
serializer.readingOptions = readingOptions;

return serializer;
}

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

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

return self;
}

相对AFHTTPResponseSerializer 设置了acceptableContentTypes

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

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

调用自身的validateResponse验证响应,
validateResponse:中如果content-type不满足,那么产生的validationError就是Domain为AFURLResponseSerializationErrorDomain,code为NSURLErrorCannotDecodeContentData,再用 AFErrorOrUnderlyingErrorHasCodeInDomain检验error 和domain,如果就是这两个值 就返回nil。

2. 解决一个由只包含一个空格的响应引起的 bug

1
2
3
4
5
BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];

if (data.length == 0 || isSpace) {
return nil;
}

对于’head :ok’,Rails返回的是一个空格 (这是Safari上的一个bug),并且这样的JSON格式不会被NSJSONSerialization解析。如果是单个空格,就不解析
查看 Issues 1742

3.序列化 JSON

1
2
3
4
5
6
7
8
9
10
11
NSError *serializationError = nil;

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

if (!responseObject)
{
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return nil;
}

使用系统自带的NSJSONSerialization来解析NSData数据,如果serializationError不为空,那么最终的error其实就是serializationError

4. 移除 JSON 中的 null

1
2
3
if (self.removesKeysWithNullValues) {
return AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}

AFJSONObjectByRemovingKeysWithNullValues通过递归的方法,把JSON中NSDictionary的数据(不包括NSArray)中的对应value为空的key移除。

AFURLRequestSerialization

AFURLRequestSerialization 的主要工作是对发出的 HTTP 请求进行处理。

使用

1
2
3
4
5
[[AFHTTPRequestSerializer serializer] requestWithMethod:@"GET" URLString:URLString parameters:parameters error:nil];

[[AFHTTPRequestSerializer serializer] requestWithMethod:@"POST" URLString:URLString parameters:parameters error:nil];

[[AFJSONRequestSerializer serializer] requestWithMethod:@"POST" URLString:URLString parameters:parameters error:nil];

AFURLRequestSerialization

AFURLRequestSerialization 是一个协议

1
2
3
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

它的实现类为:

  • AFURLRequestSerialization
    • AFHTTPRequestSerializer
      • AFJSONRequestSerializer
      • AFPropertyListRequestSerializer

AFHTTPRequestSerializer是所有类的基类,AFJSONRequestSerializer 和 AFPropertyListRequestSerializer继承了AFHTTPRequestSerializer。

AFHTTPRequestSerializer

初始化:

1
2
3
4
5
6
7
8
9
+ (instancetype)serializer {
return [[self alloc] init];
}

- (instancetype)init{
1. 初始化参数,
2. 设置HTTP 头部字段
3. KVO
}

1. 初始化参数

1
2
3
4
5
6
7

self.stringEncoding = NSUTF8StringEncoding;
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);

self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];
self.mutableObservedChangedKeyPaths = [NSMutableSet set];

初始化了一些参数 ,HTTPMethodsEncodingParametersInURI表示需要将的quey是拼接到url后面的的方法,就是GET HEAD DELETE,与此对应的POST PUT方法将query拼到http body中。mutableHTTPRequestHeaders 储存http头部信息。mutableObservedChangedKeyPaths保存需要键值观察的值。
requestHeaderModificationQueue ??

2. 设置HTTP 头部字段

1
2
3
4
5
6
7
   NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
...
[self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];

NSString *userAgent = nil;
...
[self setValue:userAgent forHTTPHeaderField:@"User-Agent"];

分别设置了acceptLanguagesComponentsuserAgent信息。
其中acceptLanguagesComponents一般为:

1
en;q=1

userAgent为:

1
[工程名字]/[工程版本] (iPhone; iOS 10.2; Scale/2.00)

3.KVO

1
2
3
4
5
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}

对一些属性进行 KVO,确保它们在改变后更新 NSMutableURLRequest 中对应的属性。其中AFHTTPRequestSerializerObservedKeyPaths为这些值:

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
//是否允许使用设备的蜂窝移动网络来创建request,默认为允许
allowsCellularAccess,
/* 创建的request所使用的缓存策略,默认使用`NSURLRequestUseProtocolCachePolicy`,该策略表示
如果缓存不存在,直接从服务端获取。如果缓存存在,会根据response中的Cache-Control字段判断
下一步操作,如: Cache-Control字段为must-revalidata, 则 询问服务端该数据是否有更新,无更新话
直接返回给用户缓存数据,若已更新,则请求服务端.*/
cachePolicy,
/**
如果设置HTTPShouldHandleCookies为YES,就处理存储在NSHTTPCookieStore中的cookies
HTTPShouldHandleCookies表示是否应该给request设置cookie并随request一起发送出去
*/
HTTPShouldHandleCookies,
/**
HTTPShouldUsePipelining表示receiver(理解为iOS客户端)的下一个信息是否必须等到上一个请求回复才能发送。
如果为YES表示可以,NO表示必须等receiver收到先前的回复才能发送下个信息。
*/
HTTPShouldUsePipelining,
/**
设定request的network service类型. 默认是`NSURLNetworkServiceTypeDefault`.
这个network service是为了告诉系统网络层这个request使用的目的
比如NSURLNetworkServiceTypeVoIP表示的就这个request是用来请求网际协议通话技术(Voice over IP)。

系统能根据提供的信息来优化网络处理,从而优化电池寿命,网络性能等等
*/
networkServiceType,
/**
超时机制,默认60秒
*/
timeoutInterval

AFURLRequestSerialization 协议的实现

初始化之后,如果调用了 - [AFHTTPSessionManager dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:],就会进入 AFHTTPRequestSerializer 的这一方法:

1
2
3
4
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error

其中调用了AFURLRequestSerialization 协议的实现:

1
2
3
4
5
6
7
8
9
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error{

1. 通过 HTTPRequestHeaders 字典设置头部字段
2. 将parameters转换为查询参数query
3. 将 query 添加到 URL 或者 HTTP body 中

}

1.通过 HTTPRequestHeaders 字典设置头部字段

1
2
3
4
5
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];

2. 将parameters转换为查询参数query

1
query = AFQueryStringFromParameters(parameters);

AFQueryStringFromParameters (调用)-> AFQueryStringPairsFromDictionary -> AFQueryStringPairsFromKeyAndValue
其中AFQueryStringPairsFromKeyAndValue递归调用并解析,返回查询参数。

3. 将 query 添加到 URL 或者 HTTP body 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}

最后这个方法会返回一个 NSMutableURLRequest。