原文:http://itangqi.me/2016/05/15/the-notes-of-learning-afnetworking-five/
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
,其主要作用为:
- 处理查询的 URL 参数
- 设置 HTTP 头部字段
- 设置请求的属性
- 分块上传
这篇文章不会对其中涉及分块上传的部分进行分析,因为其中涉及到了多个类的功能,比较复杂,如果有兴趣可以研究一下。
处理查询参数
处理查询参数这部分主要是通过 AFQueryStringPair
还有一些 C 函数来完成的,这个类有两个属性 field
和 value
对应 HTTP 请求的查询 URL 中的参数。
我们来看初始化方法,其中的 - (NSString *)URLEncodedStringValue
方法会返回 key=value
这种格式,同时使用 AFPercentEscapedStringFromString
函数来对 field
和 value
进行处理,将其中的 :#[]@!$&'()*+,;=
等字符转换为百分号表示的形式:
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 头部,管理发出的请求