在前面的几篇博客中,我们分析了AFURLSessionMangerd
以及它的子类AFHTTPSessionManager
。我们对AF的主要两个类,有了一个比较全面的了解。
对于AFHTTPSessionManager
,当其在要发送请求时,会调用AFHTTPRequestSerializer
来组装请求。 而当请求获得了响应,需要作出解析时,又会调用对应的response serializer来解析返回的data。
对于服务器响应的解析过程,在AFHTTPSessionManager
中,是通过AFHTTPResponseSerializer
来实现的。
AFHTTPResponseSerializer
作为http response的解析类,其实是作为基类存在的,对于真正的数据解析,并没有提供有用的代码,而是要让子类实现。在AFHTTPResponseSerializer
中,仅提供了最基础的配置信息,如response
接受何种语言类型,何种状态码等。以及子类的一些公共的功能函数validateResponse
:
@interface AFHTTPResponseSerializer : NSObject
- (instancetype)init;
@property (nonatomic, assign) NSStringEncoding stringEncoding DEPRECATED_MSG_ATTRIBUTE("The string encoding is never used. AFHTTPResponseSerializer only validates status codes and content types but does not try to decode the received data in any way.");
/**
Creates and returns a serializer with default configuration.
*/
+ (instancetype)serializer;
///-----------------------------------------
/// @name Configuring Response Serialization
///-----------------------------------------
@property (nonatomic, copy, nullable) NSIndexSet *acceptableStatusCodes;
@property (nonatomic, copy, nullable) NSSet *acceptableContentTypes;
- (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error;
@end
在AFHTTPResponseSerializer
的stringEncoding属性
的注释中可以看到,AFHTTPResponseSerializer
仅是指定了http response有效的status codes
和content types
,并没有对response data
做任何解析,因此,stringEncoding属性
在AFHTTPResponseSerializer
中应当是从未被使用的。
The string encoding is never used. AFHTTPResponseSerializer only validates status codes and content types but does not try to decode the received data in any way.
AFHTTPResponseSerializer
同时提供了一个父类方法validateResponse
,用来判定当前的http response是否符合设定的status codes
和content types
,这个方法,在子类中是可以通用的:
- (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]] &&
!([response MIMEType] == nil && [data length] == 0)) {
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;
}
if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
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;
}
}
if (error && !responseIsValid) {
*error = validationError;
}
return responseIsValid;
}
实现比较简单,我们就不再分析。
在AFHTTPResponseSerializer
的声明中,我们可以发现,AFHTTPResponseSerializer
是遵循AFURLResponseSerialization协议
的。而AFURLResponseSerialization协议
的定义如下:
@protocol AFURLResponseSerialization
/**
The response object decoded from the data associated with a specified response.
@param response The response to be processed.
@param data The response data to be decoded.
@param error The error that occurred while attempting to decode the response data.
@return The object decoded from the specified response data.
*/
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end
仅定义了一个方法,responseObjectForResponse:data:error:
。这个方法是用来真正的解析request请求返回的data的。其返回值为id类型
,也就是解析后的mode对象,他可以是任何类型,也就为我们解析请求的扩展,提供了可能。
对于基类AFHTTPResponseSerializer
,它responseObjectForResponse:data:error:
方法的实现是:
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
return data;
}
可以看到,其直接将data
返回,没有做任何解析。因此,需要我们继承AFHTTPResponseSerializer
,并重写responseObjectForResponse:data:error:
方法,才能够做到对响应的真正解析。
我们可以自定义子类,而AF也默认给我们提供了几个子类的实现:
/**
`AFJSONResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes JSON responses.
By default, `AFJSONResponseSerializer` accepts the following MIME types, which includes the official standard, `application/json`, as well as other commonly-used types:
- `application/json`
- `text/json`
- `text/javascript`
In RFC 7159 - Section 8.1, it states that JSON text is required to be encoded in UTF-8, UTF-16, or UTF-32, and the default encoding is UTF-8. NSJSONSerialization provides support for all the encodings listed in the specification, and recommends UTF-8 for efficiency. Using an unsupported encoding will result in serialization error. See the `NSJSONSerialization` documentation for more details.
*/
@interface AFJSONResponseSerializer : AFHTTPResponseSerializer
/**
`AFXMLParserResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLParser` objects.
By default, `AFXMLParserResponseSerializer` accepts the following MIME types, which includes the official standard, `application/xml`, as well as other commonly-used types:
- `application/xml`
- `text/xml`
*/
@interface AFXMLParserResponseSerializer : AFHTTPResponseSerializer
/**
`AFXMLDocumentResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLDocument` objects.
By default, `AFXMLDocumentResponseSerializer` accepts the following MIME types, which includes the official standard, `application/xml`, as well as other commonly-used types:
- `application/xml`
- `text/xml`
*/
@interface AFXMLDocumentResponseSerializer : AFHTTPResponseSerializer
/**
`AFPropertyListResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLDocument` objects.
By default, `AFPropertyListResponseSerializer` accepts the following MIME types:
- `application/x-plist`
*/
@interface AFPropertyListResponseSerializer : AFHTTPResponseSerializer
/**
`AFImageResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes image responses.
By default, `AFImageResponseSerializer` accepts the following MIME types, which correspond to the image formats supported by UIImage or NSImage:
- `image/tiff`
- `image/jpeg`
- `image/gif`
- `image/png`
- `image/ico`
- `image/x-icon`
- `image/bmp`
- `image/x-bmp`
- `image/x-xbitmap`
- `image/x-win-bitmap`
*/
@interface AFImageResponseSerializer : AFHTTPResponseSerializer
/**
`AFCompoundSerializer` is a subclass of `AFHTTPResponseSerializer` that delegates the response serialization to the first `AFHTTPResponseSerializer` object that returns an object for `responseObjectForResponse:data:error:`, falling back on the default behavior of `AFHTTPResponseSerializer`. This is useful for supporting multiple potential types and structures of server responses with a single serializer.
*/
@interface AFCompoundResponseSerializer : AFHTTPResponseSerializer
注意一下,最后一个AFCompoundResponseSerializer
,其实是一个集合,可以为其添加好几种ResponseSerializer
到它里面。这是为了防止在response type
未知的情况下,用组合式ResponseSerializer
来尝试是否有一种对应的解析器。
对于response 解析器的实现,每个子类都有不同的实现。但它们的共同点是:
responseObjectForResponse:data:error:
方法我们可以单独拎出来AFJSONResponseSerializer
,来看一下它是如何重写responseObjectForResponse:data:error:
方法的:
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return 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) {
return nil;
}
NSError *serializationError = nil;
id responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
if (!responseObject)
{
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return nil;
}
if (self.removesKeysWithNullValues) {
return AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
return responseObject;
}
其核心也就是调用了NSJSONSerialization
的方法,做JSON格式的解析。
上面的代码还是比较好理解的。那么,AF又是在什么时候调用Response Serializer的解析方法
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
的呢?自然,应该是在Server返回response的时候。
让我们追根溯源,还记得在基类AFURLSessionManager
中,我们曾提到过,对于每一个Session task的响应,AFURLSessionManager
都会分配一个AFURLSessionManagerTaskDelegate
来处理。其中,就包含对于系统NSURLSessionTaskDelegate
的处理。
对于NSURLSessionTaskDelegate
的
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
响应里面,AFURLSessionManagerTaskDelegate
有这么一段:
else {
dispatch_async(url_session_manager_processing_queue(), ^{
NSError *serializationError = nil;
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
。。。
}
在这里会看到manager调用了responseSerializer
对响应作出解析。而这里的responseSerializer
自然不是某个具体的类,而是符合AFURLResponseSerialization协议
的一个id类型:
id responseSerializer;
AF通过这种协议的抽象,使得我们可以在具体应用中任意替换解析对象,如,可以替换为上面提到的AFJSONResponseSerializer
,因为它正符合AFURLResponseSerialization协议
。
在AF中,对于request的序列化,同样使用了抽象协议的方式:
@protocol AFURLRequestSerialization
/**
Returns a request with the specified parameters encoded into a copy of the original request.
@param request The original request.
@param parameters The parameters to be encoded.
@param error The error that occurred while attempting to encode the request parameters.
@return A serialized request.
*/
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end
在上一章中,我们分析了AFHTTPRequestSerializer
的部分实现,它是怎么支持GET/PUT/HEAD/POST/DELETE
等method的。
在这里,我们就不再去分析其余的实现了,而是关注一下在AFHTTPRequestSerializer
中用到的一个小技巧。
在AF的AFHTTPRequestSerializer
中,需要解决这么一个问题,就是,对于NSMutableURLRequest
来说,除了设置URL和http body,还有许多控制属性可以让用户设置,如设置请求超时timeoutInterval,cache策略等。
对应的,在AFHTTPRequestSerializer
中自然也是添加了这些对应的属性来运行用户设置NSMutableURLRequest
:
@interface AFHTTPRequestSerializer : NSObject
@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;
那么,当AFHTTPRequestSerializer
在组装NSMutableURLRequest
时,又是如何知道用户设置了这些属性,并设置这些属性到NSMutableURLRequest
呢?
一般的,我们是不是想这样解决,给这每一个属性都设置一个和系统默认值相等的初始值,在组装request的时候,将这些值全部赋值给NSMutableURLRequest
? 如下面这样做:
// 先初始化一些和系统默认值相等的初始值
_allowsCellularAccess = YES;
_cachePolicy = NSURLRequestUseProtocolCachePolicy;
...
// 将这些属性赋值给request;
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
request.allowsCellularAccess = self.allowsCellularAccess;
request.cachePolicy = self.cachePolicy;
...
上面这种做法也没错,但是不是感觉有点挫?一大堆的属性赋值,但用户可能仅仅改变了其中一个或什么属性都没有改。
比较好的做法是,我们需要知道用户修改了那些属性,然后我们仅仅设置用户所修改的属性。AF确实也是这么做的,而且用了比较巧的方法。
首先,在AFHTTPRequestSerializer
中,注册了KVO监听:
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) { // 通过KVO的方式,检测并记录自身的属性变换
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext]; // AFHTTPRequestSerializerObserverContext 这到底是什么context?
}
}
AFHTTPRequestSerializer
要监听的Key数组定义为:
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;
}
对应的,重写响应属性的Set方法,并触发KVO:
- (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
[self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
_allowsCellularAccess = allowsCellularAccess;
[self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
}
我们来看KVO的响应函数:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(__unused id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == AFHTTPRequestSerializerObserverContext) {
// 将为空的参数取出,将赋值的参数记录下来,等到生成request的时候,使用这些值
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
} else {
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}
可以看到,当修改对应的属性时,KVO会通报被修改的keyPath,AF中用self.mutableObservedChangedKeyPaths
这个set来记录修改的属性的名称。
当最后要配置request时,仅需要根据这个keyPath 集合,利用KVC取出对应的属性并赋值即可:
// 3.设置mutableRequest 对应的属性值。这是通过KVO用户设置相关的属性操作
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
这是AF中关于KVO和KVC的一个很好的应用场景。
这就是我们分析AFHTTPRequestSerializer
和AFHTTPResponseSerializer
的全部内容,关于这部分的代码,并不是特别复杂,只要静下心来分析其调用路径基本就能够了解。关于AF中KVC和KVO的灵活应用,也是值得我们借鉴的地方。