上一篇整理了
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
,这一篇整理AFN的重头戏:请求。从[manager POST:parameters:progress:success:failure:]
开始梳理。
AFHTTPSessionManager 部分
日常开发中请求主要就是GET和POST两种
GET 和 POST 的声明
GET声明
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
POST声明
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
GET 和 POST 的实现
GET实现
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
[dataTask resume];
return dataTask;
}
POST实现
- (NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))uploadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters uploadProgress:uploadProgress downloadProgress:nil success:success failure:failure];
[dataTask resume];
return dataTask;
}
分解说明
- 首先要理解,AFHTTPSessionManager类的GET和POST方法内部实现的核心是:调用
dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:
方法来获取NSURLSessionDataTask。 - 这个NSURLSessionDataTask才是真正可以发起网络请求的类。而开启请求的真正方法是
resume
,当NSURLSessionDataTask调用了resume
,网络请求才发起。
GET、POST 核心方法dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:
第一部分解析
一句话描述 : 第一部分做的事情就是创建一个NSMutableURLRequest。
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
return nil;
}
先说整体逻辑
- 第一部分创建的NSMutableURLRequest是要提供给第二部分使用。
- 这个NSMutableURLRequest不是AFHTTPSessionManager类自己创建的,是通过自己的属性requestSerializer调用
requestWithMethod:URLString:parameters:error:
方法创建的。如果NSMutableURLRequest创建失败,则直接回调给开发者网络请求失败。
分解说明:
属性requestSerializer:requestSerializer是AFHTTPSessionManager类自己独有的属性,父类AFHTTPRequestSerializer没有这个属性。requestSerializer在AFHTTPSessionManager初始化的时候已经默认进行了初始化,开发者也可以随时修改此属性。初始化详情可以参考AFNetworking主线梳理(一)中的介绍。
参数completionQueue:失败回调的队列默认是主队列,如果设置了completionQueue,则走设置的队列。completionQueue是AFHTTPSessionManager父类AFURLSessionManager的属性,需要开发者来设置。
接下来,解析requestWithMethod:URLString:parameters:error:
,需要跳转到AFHTTPRequestSerializer 部分(往下翻)。
第二部分解析
一句话描述:
通过调用dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:
方法来创建一个NSURLSessionDataTask实例对象。
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(dataTask, error);
}
} else {
if (success) {
success(dataTask, responseObject);
}
}
}];
分解说明
- NSURLSessionDataTask就是前面说的GET和POST方法核心实现所需要的task。
-
dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:
方法所需要的参数request就是第一部分创建的NSMutableURLRequest实例。 - AFHTTPSessionManager自己并没有实现
dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:
方法,是它的父类AFURLSessionManager实现了这个方法。
接下来,解析dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:
方法,跳转到AFURLSessionManager 部分(使劲往下翻)。
AFHTTPRequestSerializer 部分
接续 GET、POST 核心方法dataTaskWithHTTPMethod
方法第一部分,AFHTTPRequestSerializer的requestWithMethod:URLString:parameters:error:
方法最终目的是创建一个NSMutableURLRequest实例并返回。
requestWithMethod:URLString:parameters:error:
方法解析
以下简称:requestWithMethod
方法。
requestWithMethod
方法整体逻辑:
- 使用传入的参数URLString创建一个NSURL *url
- 使用url创建一个NSMutableURLRequest *mutableRequest
- 将传入的参数 method 保存到 mutableRequest 的 HTTPMethod 属性中
- 第一部分:简单讲就是在配置NSMutableURLRequest的各种参数。
- 第二部分:简单讲其实也是在配置NSMutableURLRequest的各种参数。
- NSMutableURLRequest配置完成后,返回其实例mutableRequest。
第一部分
整体逻辑:
-
AFHTTPRequestSerializerObservedKeyPaths()
函数会创建并返回一个数组,数组中有6个属性名的字符串(keyPath)。 - for in 遍历这个数组,取出每一个属性名的字符串(keyPath)。
- 判断
self.mutableObservedChangedKeyPaths
(一个NSMutableSet)中是否包含keyPath,如果包含这个keyPath,则把AFHTTPRequestSerializer自己这个属性的值赋值给mutableRequest。
分解说明:
-
AFHTTPRequestSerializer有6个特定的属性,分别是:
@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;
这6个属性开发者可以读写,而这6个属性与NSMutableURLRequest的属性一一对应,换句话说AFHTTPRequestSerializer的6个属性最终目的是开发者设置具体值,然后传递给NSMutableURLRequest,只不过开发者从设置值到mutableRequest接收到值的过程略显复杂。
在AFHTTPRequestSerializer的
init
方法中依次对6个属性添加了键值观察(KVO)。
init
方法片段:
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
...省略...
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
return self;
}
- AFHTTPRequestSerializer重写了每一个属性的setter,成员变量Ivar赋值前后加了KVO的
willChangeValueForKey
和didChangeValueForKey
来手动触发KVO。
AFHTTPRequestSerializer的6个属性的setter实现方式完全一致,这里只贴两个属性的setter源码:
- (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
[self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
_allowsCellularAccess = allowsCellularAccess;
[self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
}
- (void)setCachePolicy:(NSURLRequestCachePolicy)cachePolicy {
[self willChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))];
_cachePolicy = cachePolicy;
[self didChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))];
}
等等......
-
AFHTTPRequestSerializerObservedKeyPaths()
函数:
下的源码就是AFHTTPRequestSerializerObservedKeyPaths()
函数的实现,其实很简单,只是创建了一个单例数组,数组中是这个6个属性的getter由selector转成的string。
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;
}
-
self.mutableObservedChangedKeyPaths
解析-
self.mutableObservedChangedKeyPaths
是什么?- 答:AFHTTPRequestSerializer的一个属性。用来保存被开发者赋值的6个属性中的一个或多个(也可以没有)。
@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;
-
self.mutableObservedChangedKeyPaths
是在什么地方被初始化的?- 答:是在AFHTTPRequestSerializer的
init
方法中被初始化的。
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
- 答:是在AFHTTPRequestSerializer的
- 案例Demo中并没有调用AFHTTPRequestSerializer的
init
方法,AFHTTPRequestSerializer的init
方法是什么时候调用的?- 答:完整的调用顺序是:
[AFHTTPSessionManager manager]
=>[AFHTTPRequestSerializer serializer]
=>[[self alloc] init]
。这里self是指AFHTTPRequestSerializer。
- 答:完整的调用顺序是:
-
self.mutableObservedChangedKeyPaths
在什么时候添加的元素?- 答:继续往下看。
-
第一部分总结:
AFHTTPRequestSerializer早在初始化的时候就对6个属性依次添加了键值观察(KVO)。如果我们在调用 AFHTTPSessionManager 的 GET 方法或者 POST 方法之前对这6个属性赋了值,就会调用属性的setter,接着会触发KVO的通知,来到KVO的observeValueForKeyPath:ofObject:change:context:
回调方法。
#pragma mark - NSKeyValueObserving
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- (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];
}
}
}
在KVO的observeValueForKeyPath:ofObject:change:context:
回调方法中,根据值是否为空,来操作self.mutableObservedChangedKeyPaths
是添加该keyPath还是移除。
我们假定开发者在调用GET或POST方法请求前设置了timeoutInterval属性:
- 走timeoutInterval属性的setter
- 触发KVO的通知,来到KVO的
observeValueForKeyPath:ofObject:change:context:
回调方法 - timeoutInterval属性被添加到了mutableObservedChangedKeyPaths集合中
- 接下来就是调用AFHTTPSessionManager的 GET 或 POST 方法发起请求
- 然后就会来到此处(requestWithMethod:URLString:parameters:error:方法的第一部分,就是现在在解析的这里)
- 接着把timeoutInterval赋值给NSMutableURLRequest。
以上就是第一部分做的全部事情。
一些补充:
-
automaticallyNotifiesObserversForKey:
函数用来根据条件阻止KVO发出通知,系统提供的方法,AFN只是进行了重写。 - 集合(set)自带去重的功能,所以当6个属性被多次赋值时,也就是多次触发observeValueForKeyPath,mutableObservedChangedKeyPaths集合(set)中不会出现多个同样的keyPath。
为什么AFN要手动触发KVO,而不是自动触发KVO ?
答:AFN的早期版本也使用KVO观察这6个属性,有人发现在单元测试中会出现崩溃,所以后来增加了automaticallyNotifiesObserversForKey方法来阻止这6个属性通过KVO自动发送通知。但是很快人们发现这样做,这6个属性使用开始出现问题,会有设置后无效的情况。于是作者又改了回去,删了automaticallyNotifiesObserversForKey方法,但是问题又绕回原地了。因此为了能在单元测试中正常使用,并且这6个属性的使用不能受到影响,即正常可以使用KVO,于是增加了6个属性的setter,在setter里面手动触发KVO。
第二部分 (高能预警)
一句话描述:
根据请求方法(GET、POST)的不同,将Parameters按照每种方法的要求组装到NSMutableURLRequest的实例mutableRequest中去。即GET方法是将参数处理后拼接到URL中,POST方法是将参数处理后设置到请求体中。
第二部分核心方法:requestBySerializingRequest:withParameters:error:
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
// 这部分起个名:self.HTTPRequestHeaders部分
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
// 这部分起个名:处理传入的parameters部分
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:
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
// 这部分起个名:使用query装配mutableRequest部分
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]];
}
return mutableRequest;
}
在源码的注释中为了方便叙述我给划分成了3个部分,分别是:
- self.HTTPRequestHeaders部分
- 处理传入的parameters部分
- 使用query装配mutableRequest部分
按照代码顺序,逐步解析:
self.HTTPRequestHeaders部分
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
一句话描述:
遍历self.HTTPRequestHeaders,如果我们创建的新 NSURLRequest *request 中没有开发者指定(init中也指定了默认)的请求头字段和值,那么把没有的这部分开发者指定的的请求头字段和值保存一份到request中。
整体逻辑
- 遍历self.HTTPRequestHeaders
- self.HTTPRequestHeaders只有初始化时AFN默认添加的
Accept-Language
、User-Agent
两个键值对。
- self.HTTPRequestHeaders只有初始化时AFN默认添加的
- 如果我们创建的新 NSURLRequest *request 中开发者指定了
Accept-Language
和User-Agent
以外的请求头键值对,那么把这些Accept-Language
与User-Agent
以外的请求头键值对保存一份到request中。
分解说明
-
self.HTTPRequestHeaders 的声明在 AFHTTPRequestSerializer.h:
@property (readonly, nonatomic, strong) NSDictionary
*HTTPRequestHeaders; -
self.HTTPRequestHeaders 的取值方法,即getter:
- (NSDictionary *)HTTPRequestHeaders { NSDictionary __block *value; dispatch_sync(self.requestHeaderModificationQueue, ^{ value = [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders]; }); return value; }
getter解析:
- self.HTTPRequestHeaders 的值完全来自于 self.mutableHTTPRequestHeaders
- requestHeaderModificationQueue 在 AFHTTPRequestSerializer 的 init 方法中初始化,是一个并发队列。具体请参考AFNetworking主线梳理(一)AFHTTPRequestSerializer 部分。
self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);
-
self.mutableHTTPRequestHeaders
- self.mutableHTTPRequestHeaders 的声明在 AFHTTPRequestSerializer.m:
@property (readwrite, nonatomic, strong) NSMutableDictionary *mutableHTTPRequestHeaders;
- self.mutableHTTPRequestHeaders 在 AFHTTPRequestSerializer 的 init 方法中初始化
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
- self.mutableHTTPRequestHeaders 如何被赋值?
- self.mutableHTTPRequestHeaders 是在
setValue:forHTTPHeaderField:
方法中间接赋值的。 - requestHeaderModificationQueue 在 AFHTTPRequestSerializer 的 init 方法中初始化,和前面第2小点的"self.HTTPRequestHeaders取值方法"用的是同一个并发队列。具体请参考AFNetworking主线梳理(一)AFHTTPRequestSerializer 部分。
- self.mutableHTTPRequestHeaders 是在
- 那么 self.mutableHTTPRequestHeaders 是什么时候被赋上默认值的呢?
- 答:在 AFHTTPRequestSerializer的init 方法中,分两次给 self.mutableHTTPRequestHeaders 赋了默认值,分别是:Accept-Language、User-Agent。具体请参考AFNetworking主线梳理(一)AFHTTPRequestSerializer 部分。
- 答:在 AFHTTPRequestSerializer的init 方法中,分两次给 self.mutableHTTPRequestHeaders 赋了默认值,分别是:Accept-Language、User-Agent。具体请参考AFNetworking主线梳理(一)AFHTTPRequestSerializer 部分。
至此,“第二部分 (高能预警)” => “self.HTTPRequestHeaders部分”的使命完成。
问:开发者如何给request指定请求头的字段和值呢?
答:通过 AFHTTPRequestSerializer 的setValue:forHTTPHeaderField:
方法。
处理传入的parameters部分
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:
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
一句话描述:“处理传入的parameters部分” 就是将parameters按照规则拼接成query string。
假定开发者传入的parameters为字典{"key1" : "value1", "key2" : "value2"}
,那么query = "key1=value1&key2=value2"。
接下来我们看看parameters是如何被拼接成query的。
“处理传入的parameters部分” 按照 if else
被自然分割成两部分,先看 if
这部分。
if
分支整体逻辑:
- 如果 self.queryStringSerialization(一个block)存在,即开发者实现了这个block,则执行
if
分支内的语句。 - 把 request、parameters 以及 *error 传递给self.queryStringSerialization这个block,由block处理完成后,返回query。
分解说明:
- self.queryStringSerialization 是一个block,需要开发者实现,声明以及赋值方法 API 如下
// AFURLRequestSerialization.h
- (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;
// AFURLRequestSerialization.m
typedef NSString * (^AFQueryStringSerializationBlock)(NSURLRequest *request, id parameters, NSError *__autoreleasing *error);
@property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization;
- (void)setQueryStringSerializationWithBlock:(NSString *(^)(NSURLRequest *, id, NSError *__autoreleasing *))block {
self.queryStringSerialization = block;
}
self.queryStringSerialization 是什么时候、什么地方被实现的?
答:没有默认的初始化,需要开发者去实现此block。如果开发者没有实现这个block,就不会走if
分支。self.queryStringSerialization 的作用
答:这个block的意义所在,是当开发者如果需要自定义 query string 的拼接规则,则用block传过来的request、parameters 以及 error 自行拼接一个 query string 并返回。NSError *__autoreleasing *error 干啥的?
答案:如果开发者在拼接 query string 过程中出现错误,想要抛出Error,就可以利用这个指针的指针来直接传递error。
else
分支整体逻辑:
可以说
else
分支直接可以看成query = AFQueryStringFromParameters(parameters);
这一句代码最为合适。
如果开发者没有使用(实现)self.queryStringSerialization,则会走else
分支,else
分支也是最普通、最常用的分支。
分解说明:
- 虽然
else
分支是一整个switch控制的,但是AFHTTPRequestQueryStringSerializationStyle枚举值只有一个而且还是 0 (NSUInteger)。也就是说开发者设置这个Style也好不设置也好,最终都会走switch的第一个case分支,目前AFN的版本中你没得选。
typedef NS_ENUM(NSUInteger, AFHTTPRequestQueryStringSerializationStyle) {
AFHTTPRequestQueryStringDefaultStyle = 0,
};
- self.queryStringSerializationStyle 是一个枚举值,需要开发者指定,声明以及赋值方法 API 如下
// AFURLRequestSerialization.h
- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style;
// AFURLRequestSerialization.m
@property (readwrite, nonatomic, assign) AFHTTPRequestQueryStringSerializationStyle queryStringSerializationStyle;
- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style {
self.queryStringSerializationStyle = style;
self.queryStringSerialization = nil;
}
重点!!!
else
分支的核心函数 AFQueryStringFromParameters()
-
AFQueryStringFromParameters()
函数
一句话描述:按照规则把parameters中所有的key和value拼接成一个字符串。
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
详细逻辑:
- 创建一个空的可变数组,NSMutableArray *mutablePairs
- 调用
AFQueryStringPairsFromDictionary()
函数获得一个数组,一个装满AFQueryStringPair
实例对象的数组。AFQueryStringPair
是AFURLRequestSerialization的私有类,具体实现贴在文末(往下翻)。
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
-
AFQueryStringPairsFromDictionary()
函数直接调用AFQueryStringPairsFromKeyAndValue()
函数
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
// 升序排序
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
// Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
}
} else { // 这个分支才是主要分支
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}
-
AFQueryStringPairsFromKeyAndValue()
函数采用递归调用方式将传进来的value(假定是字典)一层一层剥开,直到value不再是任何类型的集合,然后组装成AFQueryStringPair
实例对象添加到数组中,返回数组。- 4.1 如果传进来的value是一个字典套字典的结构:
value = {data : {adSource : Admob}}
。如果传进来的key是空,则第一次递归不重新组装函数的参数key,直接用下一层的key当做函数递归时的key。 - 4.2 假定传进来的
key
="Hello"
。 - 4.3 先把value的allKeys进行升序排序,形成一个新的数组。
- 4.4
for in
遍历这个新的数组,取出每一个key(data这一层),使用这个key从字典取出{adSource : Admob}
,即先把value这个字典剥开一层。 - 4.5 重新组装
AFQueryStringPairsFromKeyAndValue(NSString *key, id value)
函数的参数,进行递归。 - 4.6 若函数传进来的key为Hello,则递归时
key="Hello[data]",value={adSource : Admob}
。若函数传进来的key为空,则递归时key="data",value={adSource : Admob}
。 - 4.7 递归到最后,会走到
AFQueryStringPairsFromKeyAndValue()
函数的最后一个分支,即不是任何一个集合的else
分支里,此例的最终key
="Hello[data][adSource]"
或者"data[adSource]"
,最终的value="Admob"
,用这个键值对初始化一个AFQueryStringPair
实例对象,添加到数组,返回此数组。
- 4.1 如果传进来的value是一个字典套字典的结构:
- 经过3和4已经获得了2所需要的装满
AFQueryStringPair
实例对象的数组,for in
遍历这个数组中的每一个AFQueryStringPair
实例对象,将[AFQueryStringPair URLEncodedStringValue]
的返回值添加到数组。 - 第5步的数组组装完成后,大致应该是这样
("data[adSource]=Admob", "data[adType]=3")
。然后将这个数组使用"&"拆分组装成字符串,返回这个字符串,这个字符串就是query string。这个query大致长这样:"data[adSource]=Admob& data[adType]=3"
。 - query组装完成,
AFQueryStringFromParameters()
函数完成使命,返回query
至此,
AFQueryStringFromParameters()
函数的使命已完成,返回了拼接好的query。
把文章往上翻
这个query由else
分支的query接收,也就是说else
分支的使命也已完成。
再把文章往上翻
而if
分支是由开发者实现的self.queryStringSerialization
block来完成query的拼接。if
和else
分支都可完成query的拼接。
再一次把文章往上翻
“第二部分 (高能预警)” => “处理传入的parameters部分”就全部完成了,接下来该说一说“第二部分 (高能预警)” => “使用query装配mutableRequest部分”
使用query装配mutableRequest部分
一句话描述:将query装配到mutableRequest里。
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]];
}
详细逻辑:
这一部分仍然是
if else
天然分割成的两个分支。判断条件是:self.HTTPMethodsEncodingParametersInURI(NSSet)里是否包含[request HTTPMethod]
的请求方法。if
分支:如果[request HTTPMethod]
的方法是GET
、DELETE
或HEAD
这三种,则属于包含在里面。那么把请求时传进来的URL拆分,连同query一起,添加进去,将URL重新组装,再赋值给mutableRequest.URL,比如GET方法的请求参数就是拼接在URL里面。else
分支:如果[request HTTPMethod]
的方法是POST
,则不包含在里面。那么就把query按照UTF8编码成data后,调用[mutableRequest setHTTPBody:]
将POST
的请求参数(query)设置到请求体中。
分解说明:
- self.HTTPMethodsEncodingParametersInURI 是一个集合(NSSet),AFN给了默认值
GET
,HEAD
,DELETE
。开发者也可以自己指定。头文件里声明,AFJSONRequestSerializer 的init
方法设置的默认值:
// AFURLRequestSerialization.h
@property (nonatomic, strong) NSSet *HTTPMethodsEncodingParametersInURI;
// AFURLRequestSerialization.m => init 方法
self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];
[request HTTPMethod]
是什么?
答:[request HTTPMethod]
方法返回的是开发者设置到request的请求方法,诸如 GET、POST 等。-
request的请求方法是什么时候设置的?
答:完整的调用顺序是- AFHTTPSessionManager:GET或者POST方法
- AFHTTPSessionManager:[dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:]方法
- AFHTTPRequestSerializer:[requestWithMethod:URLString:parameters:error:] 方法
- AFHTTPRequestSerializer:mutableRequest.HTTPMethod = method;
self.stringEncoding 是什么?
答:字符串编码,默认为NSUTF8StringEncoding
。self.stringEncoding 是什么时候设置的默认值?
答:AFHTTPRequestSerializer 的init
方法中。
self.stringEncoding = NSUTF8StringEncoding;
至此,“第二部分 (高能预警)” => “使用query装配mutableRequest部分” 使命已完成,已成功的将query string装配到mutableRequest里。
同时也意味着“第二部分 (高能预警)”这一部分使命也已完成。
再加上第一部分,[requestWithMethod:URLString:parameters:error:]
方法的使命也已完成,创建一个NSMutableURLRequest实例,并返回给 AFHTTPSessionManager 的[dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:]
方法。
AFHTTPRequestSerializer 部分使命全部完成。AFHTTPRequestSerializer 部分已经负责创建 NSURLRequest 并返回给 AFHTTPSessionManager 的dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:
第一部分。
AFURLSessionManager 部分
接续 AFHTTPSessionManager 的 dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:
第二部分,AFURLSessionManager 部分的dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:
函数主要任务就是创建一个 NSURLSessionDataTask 实例对象,并返回。
dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:
函数解析
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {
__block NSURLSessionDataTask *dataTask = nil;
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
整体逻辑
- 创建一个局域变量:NSURLSessionDataTask *dataTask,初始化为 nil 。
- 实现
url_session_manager_create_task_safely
函数的block。 - 在block的回调中,通过属性 session 调用
dataTaskWithRequest:
方法。 -
dataTaskWithRequest:
方法会创建一个 NSURLSessionDataTask 实例对象,并返回。 - 调用
addDelegateForDataTask:uploadProgress:downloadProgress:completionHandler:
方法(参考 AFNetworking主线梳理(一))。
分解说明
-
url_session_manager_create_task_safely
函数- 函数的实现
static void url_session_manager_create_task_safely(dispatch_block_t block) { if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) { // Fix of bug // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8) // Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093 dispatch_sync(url_session_manager_creation_queue(), block); } else { block(); } }
- 函数的主要逻辑:
只有if else, 如果版本号小于iOS修复版本号,则需要把当前任务同步的添加到一个串行队列中,保证不发生并发。否则,直接调用block回调回去。 - 函数的block回调:
函数的block回调中的代码最终还是顺序执行的,无论是在if还是else里都是同步顺序执行。简单讲就是dataTask = [self.session dataTaskWithRequest:request];
这句代码是顺序执行的,就执行顺序而言与在block外无异。 - 函数的参数:
url_session_manager_creation_queue()
函数创建并返回一个类似单例管理的串行队列。
static dispatch_queue_t url_session_manager_creation_queue() { static dispatch_queue_t af_url_session_manager_creation_queue; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL); }); return af_url_session_manager_creation_queue; }
- self.session
- session是AFURLSessionManager的属性
@property (readonly, nonatomic, strong) NSURLSession *session;
- session属性是什么时候初始化的?
在AFURLSessionManager初始化方法initWithSessionConfiguration:
中进行的初始化,由AFN指定默认配置。具体请参考AFNetworking主线梳理(一)。
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
-
dataTaskWithRequest:request
是 NSURLSession 的方法,利用入参NSURLRequest创建一个 NSURLSessionDataTask 实例对象并返回。
问:为什么创建dataTask的代码要在
url_session_manager_create_task_safely
函数的block回调中执行?
答:为了解决 iOS 7 存在的bug。在 iOS 7 某些版本中如果并发创建NSURLSessionTask,会造成 NSURLSessionTask 的 taskIdentifier不唯一(有重复的),而AFN依赖 taskIdentifier 保存了很多信息,会造成混乱。
问:为什么
url_session_manager_creation_queue()
函数用单例保存队列?
个人猜测:因为该队列职责单一,无需多次重复创建新队列。网络请求多数情况下是高频的,所以多次创建不是一个好的选择。
AFQueryStringPair 部分
#pragma mark -
@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;
- (instancetype)initWithField:(id)field value:(id)value;
- (NSString *)URLEncodedStringValue;
@end
@implementation AFQueryStringPair
- (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])];
}
}
@end
AFPercentEscapedStringFromString()
没什么特殊,就是把传进去的字符串做了百分号编码的处理,防止网络请求时出现不符合规则的字符。处理完成后,返回安全的字符串。