AFURLRequestSerialization目录
AFURLRequestSerialization.h
协议属性申明
FOUNDATION_EXPORT NSString * AFPercentEscapedStringFromString(NSString *string);
FOUNDATION_EXPORT NSString * AFQueryStringFromParameters(NSDictionary *parameters);
@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying>
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error;
@end
@protocol AFMultipartFormData;
@interface AFHTTPRequestSerializer : NSObject <AFURLRequestSerialization>
@property (nonatomic, assign) NSStringEncoding stringEncoding;
@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;
@property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;
初始化与设置方法
+ (instancetype)serializer;
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(NSString *)field;
- (nullable NSString *)valueForHTTPHeaderField:(NSString *)field;
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
password:(NSString *)password;
- (void)clearAuthorizationHeader;
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;
- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style;
- (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;
GET
, HEAD
, and DELETE
请求
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error;
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable NSDictionary <NSString *, id> *)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
error:(NSError * _Nullable __autoreleasing *)error;
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
writingStreamContentsToFile:(NSURL *)fileURL
completionHandler:(nullable void (^)(NSError * _Nullable error))handler;
@end
AFMultipartFormData
@protocol AFMultipartFormData
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
error:(NSError * _Nullable __autoreleasing *)error;
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType
error:(NSError * _Nullable __autoreleasing *)error;
- (void)appendPartWithInputStream:(nullable NSInputStream *)inputStream
name:(NSString *)name
fileName:(NSString *)fileName
length:(int64_t)length
mimeType:(NSString *)mimeType;
- (void)appendPartWithFileData:(NSData *)data
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType;
- (void)appendPartWithFormData:(NSData *)data
name:(NSString *)name;
- (void)appendPartWithHeaders:(nullable NSDictionary <NSString *, NSString *> *)headers body:(NSData *)body;
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
delay:(NSTimeInterval)delay;
@end
AFJSONRequestSerializer
@interface AFJSONRequestSerializer : AFHTTPRequestSerializer
/*
typedef NS_OPTIONS(NSUInteger, NSJSONWritingOptions) {
NSJSONWritingPrettyPrinted = (1UL << 0),
NSJSONWritingSortedKeys API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0)) = (1UL << 1)
}
*/
@property (nonatomic, assign) NSJSONWritingOptions writingOptions;
+ (instancetype)serializerWithWritingOptions:(NSJSONWritingOptions)writingOptions;
@end
AFPropertyListRequestSerializer
@interface AFPropertyListRequestSerializer : AFHTTPRequestSerializer
@property (nonatomic, assign) NSPropertyListFormat format;
@property (nonatomic, assign) NSPropertyListWriteOptions writeOptions;
+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
writeOptions:(NSPropertyListWriteOptions)writeOptions;
@end
QueryString的生成
关于百分号编码
百分号编码方式:
对于Url来说,之所以要进行编码,是因为Url中有些字符会引起歧义。
例如Url参数字符串中使用key=value键值对这样的形式来传参,键值对之间以&符号分隔,如/s?q=abc& ie=utf-8。如果你的value字符串中包含了=或者&,那么势必会造成接收Url的服务器解析错误,因此必须将引起歧义的&和= 符号进行转义,也就是对其进行编码。
又如,Url的编码格式采用的是ASCII码,而不是Unicode,这也就是说你不能在Url中包含任何非ASCII字符,例如中文。否则如果客户端浏览器和服务端浏览器支持的字符集不同的情况下,中文可能会造成问题。
Url编码的原则就是使用安全的字符(没有特殊用途或者特殊意义的可打印字符)去表示那些不安全的字符。
AFPercentEscapedStringFromString特别说明
参考 https://github.com/AFNetworking/AFNetworking/pull/3028
这里解决的是:
1、部分系统内存暴涨的问题
2、特殊字符例如中文和表情的准确分割转换问题
The reason and the workaround was discovered by @PrideChung and is a memory crash issue in stringByAddingPercentEncodingWithAllowedCharacters, reported in Alamofire/Alamofire#206. Scope of this issue was refined by @cnoon as only affecting iOS7 and iOS8, not affecting iOS9:
Batching is required for escaping due to an internal bug in iOS 8.1 and 8.2. Encoding more than a few hundred Chinese characters causes various malloc error crashes. To avoid this issue until iOS 8 is no longer supported, batching MUST be used for encoding. This introduces roughly a 20% overhead.
NSString * AFPercentEscapedStringFromString(NSString *string) {
/*
①和②根据rfc3986创建在URI中query部分进行百分号编码的字符
*/
static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
//③获取iOS系统针对URI中query部分允许出现的字符集
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
//④根据rfc的规范去除①和②中出现的字符,得到最终的在query部分可以出现的字符集(不被百分号编码)
[allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
// FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
// return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
static NSUInteger const batchSize = 50;
NSUInteger index = 0;
NSMutableString *escaped = @"".mutableCopy;
while (index < string.length) {
NSUInteger length = MIN(string.length - index, batchSize);
NSRange range = NSMakeRange(index, length);
/*
To avoid breaking up character sequences such as ��������
因为iOS系统的字符串编码默认使用utf16,中文字符的长度是1,
实际上是占用两个字节,对于emoji (表情符号)等特殊字符,
这些字符的长度是4,在对其编码的时候需要按位进行,
如果只是简单的取出每一位的字符,就会产生断字。
因此需要使用字符串的rangeOfComposedCharacterSequencesForRange: 方法
达到整取的效果,比如某个字符串中有一个英文字符和3个表情符号,
长度(length)应该是13,这时候取长度10,就只能取一个英文字符和连个表情字符,
达到整取的效果,不会产生断字,影响到编码,
此处代码中有个 batchSize 设置为50,是个默认值,
这个地方你也可以这是10,或者100。
主要是为了而快速的取值,算是一个策略值吧
*/
range = [string rangeOfComposedCharacterSequencesForRange:range];
NSString *substring = [string substringWithRange:range];
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
[escaped appendString:encoded];
index += range.length;
}
return escaped;
}
parameters 转成 编码过的字符串
简单来说就是根据协议将下面的字典转成字符串
@{
@"name" : @"bang",
@"phone": @{@"mobile": @"xx", @"home": @"xx"},
@"families": @[@"father", @"mother"],
@"nums": [NSSet setWithObjects:@"1", @"2", nil]
}
第一次转换-------->>>>>>>>>
@[
field: @"name", value: @"bang",
field: @"phone[mobile]", value: @"xx",
field: @"phone[home]", value: @"xx",
field: @"families[]", value: @"father",
field: @"families[]", value: @"mother",
field: @"nums", value: @"1",
field: @"nums", value: @"2",
]
第二次转换-------->>>>>>>>>
name=bang&phone[mobile]=xx&phone[home]=xx&families[]=father&families[]=mother&nums=1&nums=2
FOUNDATION_EXPORT NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary);
FOUNDATION_EXPORT NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value);
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);//数组里面是 AFQueryStringPair
}
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;
}
NSMutableURLRequest的属性修改
设置mutableRequest的属性
AFHTTPRequestSerializerObservedKeyPaths()函数创建参数数组
①allowsCellularAccess 是否允许使用蜂窝网络
②cachePolicy 缓存策略
③HTTPShouldHandleCookies//设置请求不保存cookie
④HTTPShouldUsePipelining //是否使用流水线式请求作业(当前请求的发送需要等待上一个请求发送处理完成之后)
⑤networkServiceType //网络服务类型表示当前请求是处理那种类型的业务
⑥timeoutInterval- 默认超时时间是60s初始化的时候就添加监听
mutableObservedChangedKeyPaths 这个数组在初始化的时候创建: self.mutableObservedChangedKeyPaths = [NSMutableSet set]
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
- 设置属性变化
- (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
[self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
_allowsCellularAccess = allowsCellularAccess;
[self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
}
//将添加观察者的keypaths改为手动通知,未添加观察者的使用系统的自动通知
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
/*
这里实现的KVO实际上是在监控 如果用户手动设置了一些配置,那么可以实时修改一些配置到请求中
此处只会处理AFHTTPRequestSerializerObserverContext 类型的监听,其它的不予处理
AFHTTPRequestSerializerObserverContext是 static void * 类型,
表示此变量旨在本类的编译单元可见,指向任何类型的指针变量,赋值之后
(&AFHTTPRequestSerializerObserverContext)表示指向自己地址的指针
*/
- (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];
}
}
}
HTTPHeaderField的设置与读取
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
/**
每次获取的时候都是从 mutableHTTPRequestHeaders 中获取最新的请求头
*/
- (NSDictionary *)HTTPRequestHeaders {
NSDictionary __block *value;
dispatch_sync(self.requestHeaderModificationQueue, ^{
value = [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
});
return value;
}
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {
dispatch_barrier_async(self.requestHeaderModificationQueue, ^{
[self.mutableHTTPRequestHeaders setValue:value forKey:field];
});
}
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
NSString __block *value;
dispatch_sync(self.requestHeaderModificationQueue, ^{
value = [self.mutableHTTPRequestHeaders valueForKey:field];
});
return value;
}
- (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"];
}
- (void)clearAuthorizationHeader {
dispatch_barrier_async(self.requestHeaderModificationQueue, ^{
[self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"];
});
}
NSMutableURLRequest生成
结束了上面关于NSMutableURLRequest属性设置和HTTPHeaderField的介绍,下面就可以介绍Request生成的过程了.简单来说分为两步:
- NSMutableURLRequest的生成与用户自定义属性设置
- HTTPHeaderField设置和queryString的拼接
初始化生成Request请求
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error {
NSParameterAssert(method);
NSParameterAssert(URLString);
NSURL *url = [NSURL URLWithString:URLString];
NSParameterAssert(url);
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
// 对Request设置了自定制的一些 配置
/*---mutableObservedChangedKeyPaths 这个数组在初始化的时候创建: self.mutableObservedChangedKeyPaths = [NSMutableSet set];
使用set避免重复出现keypath
mutableObservedChangedKeyPaths 添加keypath的时机,NSKeyValueObserving
*/
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
HTTPHeaderField设置 & queryString添加
- 生成可变的NSMutableURLRequest
- 对Request设置HTTPHeaderField
- 拼接queryString(可以直接使用AFN的实现,也可以自己实现此部分)
- GET,HEAD,DELETE三种Method直接拼接在URL后面,其他的方法使用setHTTPBody方法
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error {
NSParameterAssert(request);
/*
对原始请求对象进行copy(即序列化原始request,因为从NSURLRequest定义
@interface NSURLRequest : NSObject <NSSecureCoding, NSCopying, NSMutableCopying>可以知道内部已经实现了序列化协议,可以直接使用mutableCopy来序列化)
*/
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[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://这里的解析方式在GET请求上简直就是扯犊子
/*
递归解析参数,直到出现参数中最终的结构为key :value(没有嵌套类型)时生成AFQueryStringPair对象,最终生成了所有key:value方式的AFQueryStringPair对象的数组,然后对数组中的每个对象进行百分号编码,最终使用&拼接,生成最终的query字符串
*/
query = AFQueryStringFromParameters(parameters); //book=58161&token=xxxxxxx
break;
}
}
}
//GET,HEAD,DELETE 三个请求方法 在这里拼接成一个URL字符串: https://api02u58f.zhuishushenqi.com/post/short-review/re-edit-info?book=5816b415b06d1d32157790b1&token=
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 = @"";
}
//这里估计是为了修复bug而设置的一个Content-Type
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
AFURLRequestSerialization总结
一个最简单的请求包括以下一个方面:
- Request属性设置
- HTTPHead设置
- queryString的拼接
AFNetworking所做的一切无非就是这些事情,但是细节所致,即为天使。
对于multiPart-form等请求因为使用的比较少一些,暂时没有深入的去研究,但是AFN在这方面的处理也是很到位的。