AFNetWorking(3.0)源码分析(五)——AFHTTPRequestSerializer & AFHTTPResponseSerializer

在前面的几篇博客中,我们分析了AFURLSessionMangerd以及它的子类AFHTTPSessionManager。我们对AF的主要两个类,有了一个比较全面的了解。

对于AFHTTPSessionManager,当其在要发送请求时,会调用AFHTTPRequestSerializer 来组装请求。 而当请求获得了响应,需要作出解析时,又会调用对应的response serializer来解析返回的data。

对于服务器响应的解析过程,在AFHTTPSessionManager中,是通过AFHTTPResponseSerializer来实现的。

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

AFHTTPResponseSerializerstringEncoding属性的注释中可以看到,AFHTTPResponseSerializer仅是指定了http response有效的status codescontent 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 codescontent 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来尝试是否有一种对应的解析器。

AFJSONResponseSerializer

对于response 解析器的实现,每个子类都有不同的实现。但它们的共同点是:

  1. 均继承自AFHTTPResponseSerializer
  2. 都会重写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协议

AFHTTPRequestSerializer

在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的一个很好的应用场景。

总结

这就是我们分析AFHTTPRequestSerializerAFHTTPResponseSerializer的全部内容,关于这部分的代码,并不是特别复杂,只要静下心来分析其调用路径基本就能够了解。关于AF中KVC和KVO的灵活应用,也是值得我们借鉴的地方。

你可能感兴趣的:(ios开发)