分析过AFNetworking
其实是对NSURLSession
的封装,也了解了它是如何发出请求的,在这里我们对发送请求和接收响应的过程进行序列化,这涉及到两个模块:
- AFURLRequestSerialization
- AFURLResponseSerialization
前者主要作用是修改请求(主要是HTTP
请求)的头部,提供了一些语义明确的接口设置HTTP
头部字段。后者是处理响应的模块,将请求返回的数据解析成对应的格式。
我们首先对AFURLResponseSerializer
进行简单介绍,因为这个模块使用在AFURLSessionManager
也就是核心类中,而后者AFURLRequestSerialization
主要用于AFHTTPSessionManager
中,因为它主要用于修改HTTP头部
。
AFURLResponseSerialization
其实在整个AFNetworking
项目中并不存在AFURLResponseSerialization
这个类,这只是一个协议,遵循这个协议的类会将数据解码成更用意义的表现形式。
协议的内容也非常简单,只有一个必须实现的方法:
@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end
遵循这个协议的类同时也要遵循NSObject,NSSecureCoding
和NSCopying
这三个协议,实现安全编码、拷贝以及objective-C
对象的基本行为。
仅看 AFURLResponseSerialization
协议对类的要求还是十分简单,返回对特定响应的数据解码后的对象。
在具体了解模块中类的实现之前,先看下小模块的结构:

模块中所有类都遵循AFURLResponseSerialization
协议
AFHTTPResponseSerializer
为模块中最重要的根类。
AFHTTPResponseSerializer
下面我们对模块中最重要的根类的实现进行分析,也就是AFHTTPResponseSerializer
。它是在AFURLResponseSerialization
模块中最基本的类(因为 AFURLResponseSerialization
只是一个协议)。
初始化
首先是这个类的实例化方法:
+ (instancetype)serializer {
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.stringEncoding = NSUTF8StringEncoding;
self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
self.acceptableContentTypes = nil;
return self;
}
因为是对HTTP
响应进行序列化,所有这里设置了stringEncoding
为 NSUTF8StringEncoding
而且没有对接收的内容类型加以限制。
将acceptableStatusCodes
设置为从200
到299
之间的状态码,因为只有这些状态码表示获得了有效响应。
验证响应的有效性
AFHTTPResponseSerializer
中方法的实现最长,并且最重要的就是- [AFHTTPResponseSerializer validateResponse: data: error:]
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
BOOL responseIsValid = YES;
NSError *validationError = nil;
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]]) {
#1: 返回内容类型无效
}
if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
#2: 返回状态码无效
}
}
if (error && !responseIsValid) {
*error = validationError;
}
return responseIsValid;
}
这个方法根据在初始化中初始化的属性acceptableContentTypes
和 acceptableStatusCodes
来判断当前响应是否有效。
if ([data length] > 0 && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
}
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
}
responseIsValid = NO;
其中第一、二部分的代码非常相似,出现错误时通过AFErrorWithUnderlyingError
生成格式化之后的错误,最后设置responseIsValid
。
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
}
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
responseIsValid = NO;
协议的实现
首先是对 AFURLResponseSerialization
协议的实现
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
return data;
}
调用上面的方法对响应进行验证,然后返回数据,实在是没有什么难度。
之后对NSSecureCoding
还有 NSCopying
协议的实现也都是大同小异。
AFJSONResponseSerializer
接下来,看一下 AFJSONResponseSerializer
这个继承自AFHTTPResponseSerializer
类的实现。
初始化方法只是在调用父类的初始化方法之后更新了 acceptableContentTypes
属性:
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
return self;
}
协议的实现
这个类中与父类差别最大的就是对 AFURLResponseSerialization
协议的实现。
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
#1: 验证请求
#2: 解决一个由只包含一个空格的响应引起的 bug, 略
#3: 序列化 JSON
#4: 移除 JSON 中的 null
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return responseObject;
}
-
验证请求的有效性
NSStringEncoding stringEncoding = self.stringEncoding; if (response.textEncodingName) { CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); if (encoding != kCFStringEncodingInvalidId) { stringEncoding = CFStringConvertEncodingToNSStringEncoding(encoding); } }
-
解决响应体为空,导致无法进行解析,执行错误回调的问题。
-
序列化
JSON
id responseObject = nil; NSError *serializationError = nil; @autoreleasepool { NSString *responseString = [[NSString alloc] initWithData:data encoding:stringEncoding]; if (responseString && ![responseString isEqualToString:@" "]) { // Workaround for a bug in NSJSONSerialization when Unicode character escape codes are used instead of the actual character // See http://stackoverflow.com/a/12843465/157142 data = [responseString dataUsingEncoding:NSUTF8StringEncoding]; if (data) { if ([data length] > 0) { responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError]; } else { return nil; } } else { NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"Data failed decoding as a UTF-8 string", @"AFNetworking", nil), NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Could not decode string: %@", @"AFNetworking", nil), responseString] }; serializationError = [NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo]; } } }
-
移除
JSON
中的null
if (self.removesKeysWithNullValues && responseObject) { responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions); }
其中移除JSON
中 null
函数AFJSONObjectByRemovingKeysWithNullValues
是一个递归调用的函数:
static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
if ([JSONObject isKindOfClass:[NSArray class]]) {
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
for (id value in (NSArray *)JSONObject) {
[mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
}
return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
} else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
id value = (NSDictionary *)JSONObject[key];
if (!value || [value isEqual:[NSNull null]]) {
[mutableDictionary removeObjectForKey:key];
} else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
}
}
return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
}
return JSONObject;
}
其中 移除 null 靠的就是 [mutableDictionary removeObjectForKey:key] 这一行代码。
AFURLRequestSerialization
AFURLRequestSerialization
的主要工作是对发出的HTTP
请求进行处理,它有几部分的工作需要完成。
而这个文件中的大部分类都是为了AFHTTPRequestSerializer
服务的:
-
处理查询的
URL
参数 -
设置
HTTP
头部字段 -
设置请求的属性
-
分块上传
处理查询参数
处理查询参数这部分主要是通过AFQueryStringPair
还有一些C 函数
来完成的, 这两个类有两个属性field
和value
对应HTTP
请求的查询URL
中的参数。
@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
这里- [AFQueryStringPair URLEncodedStringValue]
方法会返回 key = value
这种格式, 同时使用 AFPercentEscapedStringFromString
函数来对field
和 value
进行处理, 将其中的:#[]@!$&'()*+,;=
等字符转换为百分号表示的形式。
同时还负责返回查询参数, 将 AFQueryStringPair
或者 key value
转换为以下这种形式。
username=dravenss&password=123456&hello[world]=helloworld
它的实现主要依赖于一个递归函数AFQueryStringPairsFromKeyAndValue
,如果当前的value
是一个集合类型的话,那么它就会不断的递归调用自己。
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;
}
最后返回一个数组:
[
username=draveness,
password=123456,
hello[world]=helloworld
]
得到这个数组之后就会调用 AFQueryStringFromParameters
来使用 &来拼接它们。
static NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
设置HTTP头部字段
AFHTTPRequestSerializer
在头文件中提供一些属性方便我们设置HTTP
头部字段。同时,在类的内部,它提供了-[AFHTTPRequestSerializer setValue: forHTTPHeaderField:]
方法来设置HTTP
头部,其实它的实现都是基于一个名为mutableHTTPRequestHeaders
属性的:
- (void)setValue:(NSString *)value
forHTTPHeaderField:(NSString *)field
{
[self.mutableHTTPRequestHeaders setValue:value forKey:field];
}
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
return [self.mutableHTTPRequestHeaders valueForKey:field];
}
在设置HTTP
头部字段时,都会存储到这个可变字典中,而当真正使用时,会用 HTTPRequestHeaders
这个方法,来获取对应版本的不可变字典。
- (NSDictionary *)HTTPRequestHeaders {
return [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
}
到了这里,可以来分析一下,这个类是如何设置一些我们常用的头部字段的。首先是User-Agent
,在AFHTTPRequestSerializer
刚刚初始化时,就会根据当前编译的平台生成一个 userAgent
字符串:
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]];
[self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
设置请求的属性
还有一些 NSURLRequest
的属性是通过另一种方式来设置的,AFNetworking
为这些功能提供了接口
@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;
它们都会通过AFHTTPRequestSerializerObsservedKeyPaths
的调用而返回:
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;
}
在这些属性被设置时,会触发KVO
,然后将新的属性存储在一个名为 mutableObservedChangedKeyPaths
的字典中:
- (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];
}
}
}
然后在生成 NSURLRequest
的时候设置这些属性:
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
工作流程
AFHTTPRequestSerializer
会在 AHHTTPSessionManager
初始化是一并初始化,这时它会根据当前系统环境预设置一些HTTP
头部字段 Accept-Language User-Agent
。
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.stringEncoding = NSUTF8StringEncoding;
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
#1: 设置接收语言,用户代理,略
// 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;
}
同时它还对一些属性进行 KVO
,确保它们在改变后更新 NSMutableURLRequest
中对应的属性。
在初始化之后,如果调用了 - [AFHTTPSessionManager dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:]
,就会进入 AFHTTPRequestSerializer
的这一方法:
- (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;
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;
}
-
对参赛进行检查
-
设置
HTTP
方法mutableRequest.HTTPMethod = method;
-
通过
mutableObservedChangedKeyPaths
字典设置NSMutableURLRequest
属性for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) { [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath]; } }
-
调用
- [AFHTTPRequestSerializer requestBySerializingRequest:withParameters:error:] 设置 HTTP
头部字段和查询参数。- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error { NSParameterAssert(request); 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: query = AFQueryStringFromParameters(parameters); break; } } } if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { if (query) { 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; }
-
通过
HTTPRequestHeaders
字典设置头部字段[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { if (![request valueForHTTPHeaderField:field]) { [mutableRequest setValue:value forHTTPHeaderField:field]; }
}];
-
调用
AFQueryStringFromParameters
将参数转换为查询参数。query = AFQueryStringFromParameters(parameters);
-
将
parameters
添加到URL
或者HTTP body
中。if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { if (query) { 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]]; }
- 如果
HTTP
方法为GET HEAD
或者DELETE
,也就是在初始化方法中设置的,那么参数会追加到URL
后面。否则会被放入HTTP body
中。
- 如果
-
最后该方法会返回一个
NSMutableURLRequest
小结
- AFURLResponseSerialization
负责对返回的数据进行序列化
- AFURLRequestSerialization
负责生成 NSMutableURLRequest
,为请求设置 HTTP
头部,管理发出的请求
网友评论