前面两篇文章已经初步介绍了AFNetworking 2.x 的基本情况以及核心类AFXXXXRequestOperation的内容。主要是关于request是如何执行的,responseData如何获取的,然后关于requestSerialization&responseSerialization的内容直接跳过去的。对于requestSerialization的内容主要包括两大块:
① request的get&post参数格式化,http headers设置,请求相关其他属性的设置
② multipart request相关内容
具体涉及到的类的结构如下
分两篇文章分析这两部分内容,本篇主要是第一部分——参数格式化,headers,timeout等
1 requestSerialization完成的工作
对于一个http request,一般需要知道request的method是POST/GET,HTTP headers是什么内容,请求的url中或者HTTP body中的参数是什么,user-agent内容是什么以及request其他属性例如timeout,cache等等如何设置。总之,一切与request相关的设置都是在这个类中完成。在上述工作中,最难的地方就是传入的parameter参数如何转化成request可以使用的参数结构。另一个难点是构建multipart 的http body。
对于格式化请求参数,在request过程中一般会增加参数例如username=brownfeng&company=webank
,POST方法放在http body,GET方法放在URL的?
后面。AFNetworking提供了方法,让我们将dict,array,set表示的参数转化成key=value
形式,框架中用AFQueryStringPair表示,然后拼接成key1=value1&key2=value2
根据http request method的不同,放到url中(进行过url encoded)或者http body中。
大致的流程如下:参考https://github.com/AFNetworking/AFNetworking/tree/2.x
对于GET请求
NSString *URLString = @"http://example.com";
NSDictionary *parameters = @{@"foo": @"bar", @"baz": @[@1, @2, @3]};
[[AFHTTPRequestSerializer serializer] requestWithMethod:@"GET" URLString:URLString parameters:parameters error:nil];
--------->
GET http://example.com?foo=bar&baz[]=1&baz[]=2&baz[]=3
对于POST请求:
[[AFHTTPRequestSerializer serializer] requestWithMethod:@"POST" URLString:URLString parameters:parameters];
--------->
POST http://example.com/
Content-Type: application/x-www-form-urlencoded
foo=bar&baz[]=1&baz[]=2&baz[]=3
另一个更加细致的parameter -> query string, 参考http://blog.cnbang.net/tech/2371/
@{
@"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&num=2
AFNetworking不仅仅支持默认的这种格式化,也支持另外两种格式:AFJSONRequestSerializer&AFPropertyListRequestSerializer。这两种格式的转化方法比较简单,直接调用的系统api,本文就不深入讨论。
另外,如果这三种格式都不满足需求,还可以模仿上述格式化方法自定义的格式——只需要继承AFHTTPRequestSerializer,并实现AFURLRequestSerialization protocol,然后在AFHTTPRequestManager中设置requestSerializer
属性为自定义的对象即可。
2 requestSerialization创建
如果阅读过第一篇文章,就会有知道, AFHTTPRequestOperationManager有一个AFHTTPRequestSerializer属性,并在designed init 方法中初始化,当我们调用manager方法时,就会触发[AFHTTPRequestSerializer serializer]
(这里也可以设置成AFJSONRequestSerializer & AFPropertyListRequestSerializer)。这是一个类方法,AFHTTPRequestSerializer中的designed init方法如下,主要设置NSURLRequest的http headers:Accept-Language, User-Agent等等
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.stringEncoding = NSUTF8StringEncoding;//request body 的encoding,如果有的话
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];//初始化请求的headers
// 设置HTTP请求request的Accept-Language属性
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"];
//设置 UA
NSString *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]];
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 需要直接在uri 中 encode parameter
self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];
//对allowsCellularAccess,cachePolicy,HTTPShouldHandleCookies,HTTPShouldUsePipelining,networkServiceType,timeoutInterval等属性进行观察
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;
}
3 默认requestSerializer请求序列化的过程
如果阅读过第一篇文章,就会有知道,requestSerializaiton发生在 AFHTTPRequestOperationManager的
HTTPRequestOperationWithHTTPMethod:URLString:parameters:success:failure:方法中,会返回一个NSMutableURLRequest。
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
而上述方法的实现如下:
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
//使用宏定义判断是否为nil
NSParameterAssert(method);
NSParameterAssert(URLString);
//创建url
NSURL *url = [NSURL URLWithString:URLString];
NSParameterAssert(url);
//创建mutableURLRequest,并设置request method
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
//十分巧妙的通过serializer的属性设置request的部分属性(值得学习的方法)
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
//序列化的核心message
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
方法(NSURLRequest *)requestBySerializingRequest:withParameters:error:
是所有序列化方法里面最重要的方法——根据请求是POST or GET设置请求参数到url or body,然后Post情况下设置content-type。重点是如何从paramter中取出参数然后拼成request需要的形式。
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
//通过serializer 的headers属性 设置 request 的headers
[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) {//自定义的序列化的block,如果不为空
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
} else {
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
//调用系统的parameter -> queryString 方法,生成样式 xxx=xx&xxx=xx,对于转化成json&property模式替换此方法
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
//如果HTTP method 是 GET
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {//如果HTTP method 是 POST
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
//设置Content-Type
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
//将query 放到HTTPBody中
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
通过源码可以发现如果设置了queryStringSerialization Block,AFNetworking就会调用这个block来序列化query参数,也可以将这部分参数添加到系统中,定义成一个style来选择。
关键代码是(id)paramters -> (NSString *)query 有关参数的组装拼接主要包括以下方法&属性
@property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization
static NSString * AFQueryStringFromParameters(NSDictionary *parameters)
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary)
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value)
具体的实现如下:
//网络框架的 parameters->query 入口方法,系统只支持paramters是NSDictionary*的参数列表,如果需要解析其他parameters的形式,比如model||array等等,需要自己实现queryStringSerialization block。也可以在AFHTTPRequestManager进行封装,增加其他的添加参数的形式,本系列最后会给一个传入model当做参数的实例。
static 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);
}
//一个递归调用的方法:递归的判断传入的value是否是dict,array,set,非集合类型,如果是value是集合类型,dict,set无序集合需要先排序再处理,继续递归,如果是非集合类型则会返回,最后得到一个pair类型的array,返回上一层进行foreach操作,输出pair 的URLEncodedStringValue(url编码)值的数组,然后组装成'&'间隔的字符串paramters。
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 {//如果非集合类,直接当做key-value pair
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}
总结经验:
① 使用#define NSParameterAssert(condition) NSAssert((condition), @"Invalid parameter not satisfying: %@", @#condition)
宏定义来判断参数是否为nil
② 将parameter转化成key-value pair的方法值得借鉴,在格式化其他内容时候使用递归调用的方式处理。最后一篇文章会给出一个实例。
③ 使用KVO的方式设置request的其他属性(如果没有手动设置requestSerializer的这几个属性,以下几个setter方法不会出发,生成的request相关属性都是默认的),使用流程如下:
- 在init方法中调用方法如下方法,对需要设置的属性进行KVO观察
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
- 在每个观察属性的getter方法中willChange 和 didChange方法 send message
- (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))];
}
- (void)setHTTPShouldHandleCookies:(BOOL)HTTPShouldHandleCookies {
[self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
_HTTPShouldHandleCookies = HTTPShouldHandleCookies;
[self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
}
- (void)setHTTPShouldUsePipelining:(BOOL)HTTPShouldUsePipelining {
[self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))];
_HTTPShouldUsePipelining = HTTPShouldUsePipelining;
[self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))];
}
- (void)setNetworkServiceType:(NSURLRequestNetworkServiceType)networkServiceType {
[self willChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))];
_networkServiceType = networkServiceType;
[self didChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))];
}
- (void)setTimeoutInterval:(NSTimeInterval)timeoutInterval {
[self willChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))];
_timeoutInterval = timeoutInterval;
[self didChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))];
}
- 最后在属性发生变化时,将key加入到mutableObservedChangedKeyPaths中,下次调用requestXXXXX方法生成request对象时,就可以讲设置的相关属性设置到request对象中。
+ (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];
}
}
}
- 在
- (NSMutableURLRequest *)requestWithMethod:URLString:parameters:error:
方法中调用如下语句,返回mutableRequest时候就会去设置前面加入到mutableObservedChangedKeyPaths的属性
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}