原文链接:http://itangqi.me/2016/05/13/the-notes-of-learning-afnetworking-four/
前言
通过前面的文章,我们已经知道 AFNetworking
是对 NSURLSession
的封装,也了解它是如何发出请求的,在这里我们对发出请求以及接收响应的过程进行序列化,这涉及到两个模块:
前者是处理响应的模块,将请求返回的数据解析成对应的格式。而后者的主要作用是修改请求(主要是 HTTP 请求)的头部,提供了一些语义明确的接口设置 HTTP 头部字段。
我们首先会对 AFURLResponseSerialization
进行简单的介绍,因为这个模块使用在 AFURLSessionManager
也就是核心类中,而后者 AFURLRequestSerialization
主要用于 AFHTTPSessionManager
中,因为它主要用于修改 HTTP 头部。
AFURLResponseSerialization
在了解模块中类的具体实现之前,先看一下模块的结构图:
AFURLResponseSerialization
定义为协议,且协议的内容非常简单,只有一个必须实现的方法:
1 2 3 4 5 6 7 | @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
协议 AFHTTPResponseSerializer
为模块中最终要的根类
AFHTTPResponseSerializer
下面我们对模块中最重要的根类,也就是 AFHTTPResponseSerializer
的实现进行分析。它是在 AFURLResponseSerialization
模块中最基本的类(因为 AFURLResponseSerialization
只是一个协议)
初始化
首先,依然从实例化方法入手:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | + (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 之间的状态码, 因为只有这些状态码表示获得了有效的响应。
补充:HTTP状态码
验证响应的有效性
AFHTTPResponseSerializer
中方法的实现最长,并且最重要的就是 - [AFHTTPResponseSerializer validateResponse:data:error:]
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | - (BOOL)validateResponse:(NSHTTPURLResponse *)response data:(NSData *)data error:(NSError * __autoreleasing *)error { BOOL responseIsValid = YES; NSError *validationError = nil; // 简单的为空判断和类型判断,注意如果 response 为空或类型不对,反而 responseValid 为 YES if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) { if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]]) { truetruetrue#1: 返回内容类型无效 } if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) { truetruetrue#2: 返回状态码无效 } } if (error && !responseIsValid) { *error = validationError; } return responseIsValid; } |
这个方法根据在初始化方法中初始化的属性 acceptableContentTypes
和 acceptableStatusCodes
来判断当前响应是否有效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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
。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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
协议的实现:
1 2 3 4 5 6 7 8 | - (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { [self validateResponse:(NSHTTPURLResponse *)response data:data error:error]; return data; } |
调用上面的方法对响应进行验证,然后返回数据,并没有复杂的逻辑。
AFJSONResponseSerializer
接下来,看一下 AFJSONResponseSerializer
这个继承自 AFHTTPResponseSerializer
类的实现。
初始化方法只是在调用父类的初始化方法之后更新了 acceptableContentTypes
属性:
1 2 3 4 5 6 7 8 9 10 | - (instancetype)init { self = [super init]; if (!self) { return nil; } self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil]; return self; } |
协议的实现
这个类中与父类差别最大的就是对 AFURLResponseSerialization
协议的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | - (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { #1: 验证请求 #2: 解决一个由只包含一个空格的响应引起的 bug, 略 #3: 序列化 JSON true #4: 移除 JSON 中的 null if (error) { *error = AFErrorWithUnderlyingError(serializationError, *error); } return responseObject; } |
-
验证请求的有效性
1 2 3 4 5
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) { if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) { return nil; } }
-
解决一个空格引起的 bug,见 https://github.com/rails/rails/issues/1742
-
序列化 JSON
1 2 3 4 5 6 7 8 9 10
id responseObject = nil; NSError *serializationError = nil; // Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization. // See https://github.com/rails/rails/issues/1742 BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]]; if (data.length > 0 && !isSpace) { responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError]; } else { return nil; }
-
移除 JSON 中的 null
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
trueif (self.removesKeysWithNullValues && responseObject) { true responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions); true} true``` 其中移除 JSON 中 null 的函数 `AFJSONObjectByRemovingKeysWithNullValues` 是一个递归调用的函数: ```objectivec 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]
这一行代码。
注:
AFXMLParserResponseSerializer
、AFXMLDocumentResponseSerializer
、AFPropertyListResponseSerializer
、 AFImageResponseSerializer 及AFCompoundResponseSerializer
将留给感兴趣的同学。