AFURLRequestSerialization
主要是对请求进行编码.
字符转码
当发送的网络请求URL中包含了特殊字符, 造成URL无法被解析时, 那这个请求就会发送失败, 出现问题.
所以在发送网络请求之前, 需要对URL进行编码, 对特殊字符进行处理. RFC 3986
规定URL中允许包含的字符有英文字母(a~z A~Z) 数字(0~9)以及下列保留字符等.
RFC 3986
中定义的保留字符有
- General Delimiters: ":", "#", "[", "]", "@", "?", "/"
- Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
URL的编码通常采用百分号编码, 百分号编码是使用% + (0123456789ABCDEF)中的两位
代表一个字符的十六进制形式, URL的编码字符集是US-ASCII, 比如a的ASCII是0x61, 那url编码以后就是%61
. 再比如abc经过URL编码以后就是%61%62%63
.
1. AFURLRequestSerialization协议
AFURLRequestSerialization
是一个协议, 请求序列化将参数编码为查询字符串、HTTP主体、必要时设置适当的HTTP头字段.
AFURLRequestSerialization
协议中声明了一个方法:
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
根据指定的参数parameters
将request
进行编码, 并将编码以后的request
进行返回.
AFURLRequestSerialization
文件中声明了三种序列化器, 分别为:
AFHTTPRequestSerializer
实现了AFURLRequestSerialization协议, 查询字符串/URL表单编码的参数序列化和默认的请求头,以及响应状态代码和内容类型验证.
AFJSONRequestSerializer
是AFHTTPRequestSerializer
的一个子类, 将parameters
参数使用NSJSONSerialization序列化为JSON, 并且设置Content-Type
为application/json
.
AFPropertyListRequestSerializer
是AFHTTPRequestSerializer
的一个子类, 将parameters
参数使用NSPropertyListSerializer序列化为JSON, 并且设置Content-Type
为application/x-plist
.
2. 对外提供的序列化函数
FOUNDATION_EXPORT NSString * AFPercentEscapedStringFromString(NSString *string);
FOUNDATION_EXPORT NSString * AFQueryStringFromParameters(NSDictionary *parameters);
个人理解为对外提供了两个全局函数
AFPercentEscapedStringFromString
:将指定的string字符串进行百分号编码.
AFQueryStringFromParameters
:将指定的parameters
字典转换为查询字符串,
3. 在AFN中的使用
我们来看一下AFN
是如何使用AFHTTPRequestSerializer
进行编码的, 在AFHTTPSessionManager
的dataTaskWithHTTPMethod
方法中进行构建NSMutableURLRequest
的方法如下.
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
......
}
self.requestSerializer
如果没有特殊设置这里默认的是AFHTTPRequestSerializer, 所以这里我们看一下AFHTTPRequestSerializer的序列化方法.
4. AFHTTPRequestSerializer构建
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.stringEncoding = NSUTF8StringEncoding;
// 存储请求头的字典
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
// 请求头改变的时候在本队列执行, 并行队列
self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);
// Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
[[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
float q = 1.0f - (idx * 0.1f);
[acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
*stop = q <= 0.5f;
}];
[self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];
NSString *userAgent = nil;
#if TARGET_OS_IOS
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
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]];
#elif TARGET_OS_WATCH
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; 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], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
#endif
if (userAgent) {
if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
NSMutableString *mutableUserAgent = [userAgent mutableCopy];
if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
userAgent = mutableUserAgent;
}
}
[self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
}
// 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;
}
AFHTTPRequestSerializer的创建也是很重要的一部分.
stringEncoding
指定默认的编码方式为UTF8编码.
mutableHTTPRequestHeaders
保存了我们修改的请求头的信息, 当对请求头做修改时会在requestHeaderModificationQueue
并行队列中执行, 将修改的信息保存在mutableHTTPRequestHeaders
字典中, 在调用requestBySerializingRequest
对请求编码的时候遍历这个字典, 给request设置header.
acceptLanguagesComponents
表示客户端支持的语言.
userAgent
将客户端的环境通过User-Agent
字段传给服务器.
通过AFHTTPRequestSerializerObservedKeyPaths
函数获取AFN监听哪些头部字段的变化, 并且提供监听方法, 在监听方法中如果有值改变, 就赋值给mutableHTTPRequestHeaders
字典.
5. AFURLRequestSerialization序列化方法
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
// 从自己的head遍历, 如果有值就给request的header赋值
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
// 把各种类型的参数, 转成string
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:
// 将传入的paramter参数, 用=号链接(key1=name1&key2=name2)
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
// HTTPMethodsEncodingParametersInURI, 默认为`GET`, `HEAD`, and `DELETE`
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
// 拼接URL
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;
}
将传入的parameters参数序列化到request中.
先遍历HTTPRequestHeaders
字典, 将遍历到的值设置到request请求头中.
如果有自定的queryStringSerialization
序列化方式, 就执行自定义, 否则按照默认的方式序列化. 这里调用了AFN的自定义函数AFQueryStringFromParameters
进行序列化, 下边会讲解这个函数.
比如我们传进来的参数是@{@"key1" : @"name1", @"key2" : @"name2", @"key3" : @"哈哈"}
, 参数会经过百分号编码, 执行AFQueryStringFromParameters
序列化之后是key1=name1&key2=name2&key3=%E5%93%88%E5%93%88
.
如果request的HTTPMethod是GET, HEAD或者DELETE, 就把经过序列化以后的查询字符串拼接到request的URL中. 否则将查询字符串query设置到request的请求体中, 至此request请求设置完毕.
application/x-www-form-urlencoded:数据被编码为名称/值对。这是标准的编码格式。(默认使用这种格式) multipart/form-data: 数据被编码为一条消息,页上的每个控件对应消息中的一个部分。(上传文件需要用到这种) text/plain: 数据以纯文本形式(text/json/xml/html)进行编码,其中不含任何控件或格式字符。postman软件里标的是RAW。
6.AFURLRequestSerialization中的辅助函数
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
/*{
@"key1" : @"value1",
@"key2" : @"value2"
}*/
/**
对参数进行转码,
*/
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;
}