AFNetworking 3.0 源码阅读笔记(五)

AFURLRequestSerialization

AFURLRequestSerialization 定义为协议,其主要工作是对发出的 HTTP 请求进行处理:

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

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

@end

遵循该协议的类同时也要遵循 NSObject、NSSecureCoding 和 NSCopying 这三个协议,以实现 Objective-C 对象的基本行为、安全编码以及拷贝。

AFHTTPRequestSerializer

而在 AFURLRequestSerialization 模块中,最为重要的类便是 AFHTTPRequestSerializer,其主要作用为:

  1. 处理查询的 URL 参数
  2. 设置 HTTP 头部字段
  3. 设置请求的属性
  4. 分块上传

这篇文章不会对其中涉及分块上传的部分进行分析,因为其中涉及到了多个类的功能,比较复杂,如果有兴趣可以研究一下。

处理查询参数

处理查询参数这部分主要是通过 AFQueryStringPair 还有一些 C 函数来完成的,这个类有两个属性 fieldvalue 对应 HTTP 请求的查询 URL 中的参数。

我们来看初始化方法,其中的 - (NSString *)URLEncodedStringValue 方法会返回 key=value 这种格式,同时使用 AFPercentEscapedStringFromString 函数来对 fieldvalue 进行处理,将其中的 :#[]@!$&'()*+,;= 等字符转换为百分号表示的形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (instancetype)initWithField:(id)field value:(id)value {
self = [super init];
if (!self) {
return nil;
}

self.field = field;
self.value = value;

return self;
}

- (NSString *)URLEncodedStringValue {
if (!self.value || [self.value isEqual:[NSNull null]]) {
return AFPercentEscapedStringFromString([self.field description]);
} else {
return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
}
}

这一部分代码还负责返回查询参数,将 AFQueryStringPair 或者 key value 转换为以下这种形式:

1
username = dravenss&password=123456&hello[world]=helloworld

它的实现主要依赖于一个递归函数 AFQueryStringPairsFromKeyAndValue,如果当前的 value 是一个集合类型的话,那么它就会不断地递归调用自己:

1
2
3
4
5
6
7
8
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
...

return mutableQueryStringComponents;
}

最后返回一个数组

1
2
3
4
5
[
username = draveness,
password = 123456,
hello[world] = helloworld
]

得到这个数组之后就会调用 AFQueryStringFromParameters 使用 & 来拼接它们:

1
2
3
4
5
6
7
8
static NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}

return [mutablePairs componentsJoinedByString:@"&"];
}

设置 HTTP 头部字段

AFHTTPRequestSerializer 在头文件中提供了一些属性方便我们设置 HTTP 头部字段。同时,在类的内部,它提供了 - [AFHTTPRequestSerializer setValue:forHTTPHeaderField:] 方法来设置 HTTP 头部,其实它的实现都是基于一个名为 mutableHTTPRequestHeaders 的属性的:

1
2
3
4
5
6
7
8
9
- (void)setValue:(NSString *)value
forHTTPHeaderField:(NSString *)field
{
[self.mutableHTTPRequestHeaders setValue:value forKey:field];
}

- (NSString *)valueForHTTPHeaderField:(NSString *)field {
return [self.mutableHTTPRequestHeaders valueForKey:field];
}

在设置 HTTP 头部字段时,都会存储到这个可变字典中。而当真正使用时,会用 HTTPRequestHeaders 这个方法,来获取对应版本的不可变字典:

1
2
3
- (NSDictionary *)HTTPRequestHeaders {
return [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
}

到了这里,可以来分析一下,这个类是如何设置一些我们平时常用的头部字段的。首先是 User-Agent,在 AFHTTPRequestSerializer 刚刚初始化时,就会根据当前编译的平台生成一个 userAgent 字符串:

1
2
3
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];

[self setValue:userAgent forHTTPHeaderField:@"User-Agent"];

设置验证字段时,可以使用 - [AFHTTPRequestSerializer setAuthorizationHeaderFieldWithUsername:password:] 方法:

1
2
3
4
5
6
7
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
password:(NSString *)password
{
NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
[self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"];
}

设置请求的属性

还有一些 NSURLRequest 的属性是通过另一种方式来设置的,AFNetworking 为这些功能提供了接口:

1
2
3
4
5
6
7
8
9
10
11
@property (nonatomic, assign) BOOL allowsCellularAccess;

@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;

@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;

@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;

@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;

@property (nonatomic, assign) NSTimeInterval timeoutInterval;

它们都会通过 AFHTTPRequestSerializerObservedKeyPaths 的调用而返回:

1
2
3
4
5
6
7
8
9
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
});

return _AFHTTPRequestSerializerObservedKeyPaths;
}

在这些属性被设置时,会触发 KVO,然后将新的属性存储在一个名为 mutableObservedChangedKeyPaths 的字典中:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(__unused id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == AFHTTPRequestSerializerObserverContext) {
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
} else {
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}

然后会在生成 NSURLRequest 的时候设置这些属性:

1
2
3
4
5
6
7
8
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;

for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}

关于这个方法的的具体实现会在下一节中介绍。

工作流程

AFHTTPRequestSerializer 会在 AHHTTPSessionManager 初始化时一并初始化,这时它会根据当前系统环境预设置一些 HTTP 头部字段 Accept-Language User-Agent

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
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}

self.stringEncoding = NSUTF8StringEncoding;

self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];


#1: 设置接收语言,用户代理,略

// HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];

self.mutableObservedChangedKeyPaths = [NSMutableSet set];
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}

return self;
}

同时它还对一些属性进行 KVO,确保它们在改变后更新 NSMutableURLRequest 中对应的属性。

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

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

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(method);
NSParameterAssert(URLString);

NSURL *url = [NSURL URLWithString:URLString];
// 1. 对参数进行检查
NSParameterAssert(url);

NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
// 2. 设置 HTTP 方法
mutableRequest.HTTPMethod = method;
// 3. 通过 `mutableObservedChangedKeyPaths` 字典设置 `NSMutableURLRequest` 的属性
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
// 设置 HTTP 头部字段和查询参数
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

return mutableRequest;
}

- [AFHTTPRequestSerializer requestBySerializingRequest:withParameters:error:] 方法主要做了以下几件事情:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);

NSMutableURLRequest *mutableRequest = [request mutableCopy];
// 1. 通过 `HTTPRequestHeaders` 字典设置头部字段
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];

NSString *query = nil;
if (parameters) {
if (self.queryStringSerialization) {
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);

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

return nil;
}
} else {
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
// 2. 调用 `AFQueryStringFromParameters` 将参数转换为查询参数
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
// 3. 将 parameters 添加到 URL 或者 HTTP body 中
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query) {
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]];
}
// 4. 将 parameters 添加到 URL 或者 HTTP body 中
return mutableRequest;
}

小结

  • AFURLResponseSerialization 负责对返回的数据进行序列化
  • AFURLRequestSerialization 负责生成 NSMutableURLRequest,为请求设置 HTTP 头部,管理发出的请求

参考