AFNetworking是Objective-C最经典的网络请求库了,目前GitHub star 数量为31.1k。解读赏析经典源码是提高自身技术,加强对技术深度理解的一条快捷之路。本文旨在梳理AFNetworking网络是怎么开始加载网络,到网络返回之后的数据处理等一系列操作。
一、网络发起前对数据的处理
AFNetworking 3.x发起http网络请求,主要使用AFHTTPSessionManager
,关于下载和上传,后面会说道。
AFHTTPSessionManager
中好几个方法都是是对下面这个方法的封装,包扩HTTPMethod为GET、HEAD、POST、PUT、PATCH、DELETE的几个方法。
- (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
而发起请求是使用的 NSURLSessionDataTask
,在发起请求之前,首先生成对应的NSMutableURLRequest
1. 设置URL
一般情况,初始化AFHTTPSessionManager
时,要设置baseURL,设置过baseURL后,在指定URLString时直接使用URL path就可以了,并且在使用非baseURL的完整链接时不影响正确性。根据NSMutableURLRequest的生成来看
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
baseURL是通过URLWithString:relativeToURL:
方法来使用的,下面是AFNetworking关于此方法的官方示例。
NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"];
[NSURL URLWithString:@"foo" relativeToURL:baseURL]; // http://example.com/v1/foo
[NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL]; // http://example.com/v1/foo?bar=baz
[NSURL URLWithString:@"/foo" relativeToURL:baseURL]; // http://example.com/foo
[NSURL URLWithString:@"foo/" relativeToURL:baseURL]; // http://example.com/v1/foo
[NSURL URLWithString:@"/foo/" relativeToURL:baseURL]; // http://example.com/foo/
[NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/
2. AFHTTPRequestSerializer
生成NSMutableURLRequest的主要是AFHTTPRequestSerializer
类。此类是网络发起前对数据的处理核心类。AFHTTPRequestSerializer
在生成NSMutableURLRequest
时,有如下几个步骤:
2.1 为每个请求添加映射属性
NSMutableURLRequest有allowsCellularAccess
、cachePolicy
、HTTPShouldHandleCookies
、HTTPShouldUsePipelining
、networkServiceType
、timeoutInterval
六个属性,被映射到AFHTTPRequestSerializer中,在AFHTTPRequestSerializer类里设置这六个属性,生成NSMutableURLRequest时,会被逐个复制到NSMutableURLRequest中。例如AFHTTPRequestSerializer类中,在allowsCellularAccess
的注释里,作者这么写:
Whether created requests can use the device’s cellular radio (if present).
YES
by default.
@see NSMutableURLRequest -setAllowsCellularAccess:
AFHTTPRequestSerializer类的allowsCellularAccess
,可直接去参见NSMutableURLRequest 的-setAllowsCellularAccess:
,其他属性类似。作者写设置这几个属性的方式也是相当经典的写法。首先,在AFHTTPRequestSerializer中设置一个单例数组,用来存储映射属性,然后初始化AFHTTPRequestSerializer时,用观察者观察数组中每一个属性的值的变化,当设置单例数组中任何一个属性后,AFHTTPRequestSerializer就会把该属性的方法名添加到mutableObservedChangedKeyPaths中,最后在生成mutableRequest时,逐个设置到mutableRequest。如下:
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
//AFHTTPRequestSerializerObservedKeyPaths()
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;
}
以下是这几个属性的意思
allowsCellularAccess 设置是否允许蜂窝网络访问,默认YES
cachePolicy 缓存行为 NSURLRequestCachePolicy类
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
//对特定的 URL 请求使用网络协议中实现的缓存逻辑。这是默认的策略。
NSURLRequestUseProtocolCachePolicy = 0,
//数据需要从原始地址加载。不使用现有缓存。
NSURLRequestReloadIgnoringLocalCacheData = 1,
//不仅忽略本地缓存,同时也忽略代理服务器或其他中间介质目前已有的、协议允许的缓存。
NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
//无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,那么从原始地址加载数据。
NSURLRequestReturnCacheDataElseLoad = 2,
//无论缓存是否过期,先使用本地缓存数据。如果缓存中没有请求所对应的数据,
//那么放弃从原始地址加载数据,请求视为失败(即:“离线”模式)。
NSURLRequestReturnCacheDataDontLoad = 3,
//从原始地址确认缓存数据的合法性后,缓存数据就可以使用,否则从原始地址加载。
NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented
};
HTTPShouldHandleCookies 是否使用NSURLRequest发送合适的存储cookie,默认YES
HTTPShouldUsePipelining
通常默认情况下请求和响应是顺序的,也就是说请求–>得到响应后,再请求。 如果将HTTPShouldUsePipelining设置为YES, 则允许不必等到response, 就可以再次请求。这个会很大的提高网络请求的效率,但是也可能会出问题。
因为客户端无法正确的匹配请求与响应,所以这依赖于服务器必须保证,响应的顺序与客户端请求的顺序一致。如果服务器不能保证这一点,那可能导致响应和请求混乱。
networkServiceType 网络请求的服务类型,是个枚举。在Voip,请求视频,后台下载,语音请求的时候可以特殊设置。
timeoutInterval 超时时间间隔,默认是60秒。
2.2 设置http header
在NSURLRequest 设置http header时,使用- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field
,这个方法,在AFHTTPRequestSerializer中有映射。在调用这个方法后,Header被存在mutableHTTPRequestHeaders
字典中,AFHTTPRequestSerializer生成NSMutableURLRequest时,会逐个设置到Request中。代码如下:
//HTTPRequestHeaders即由mutableHTTPRequestHeaders生成
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
2.3 请求参数的序列化
AFHTTPRequestSerializer支持参数自定义序列化只需要实现queryStringSerialization
属性,这是AFQueryStringSerializationBlock。
默认情况下,如果传递参数字典后会使用NSString * AFQueryStringFromParameters(NSDictionary *parameters)
方法来拼装成QueryString。
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;
}
}
}
AFQueryStringFromParameters()
方法参数序列化
主要使用NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value)
方法得到AFQueryStringPair
的数组,然后利用For循环进行拼接。AFQueryStringFromParameters
拼接“&”,AFQueryStringPair的URLEncodedStringValue
用来拼接“=”
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;
}
AFQueryStringPair 用来存储key-value,然后使用URLEncodedStringValue
方法拼接“=”,而AFPercentEscapedStringFromString()
是对参数里的字段进行编码,可参见RFC3986文档。这个博客里也有介绍。
@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
2.4 生成NSMutableURLRequest
//HTTPMethodsEncodingParametersInURI用来存储请求方法
//包括@"GET", @"HEAD", @"DELETE",当请求是这三种时,
//直接将参数query拼接在链接里。
//不是这三种,则将参数query转化为NSData设置为HTTPBody。编码方式默认为NSUTF8StringEncoding
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]];
}
参考:
1.NSURLRequestCachePolicy
2.HTTPShouldUsePipelining解释
3.RFC3986之URL编码与解码、AFPercentEscapedStringFromString