1. 程式人生 > >AFNetWorking(3.0)原始碼分析(五)——AFHTTPRequestSerializer & AFHTTPResponseSerializer

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 <AFURLResponseSerialization>

- (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 <NSString *> *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 <NSObject, NSSecureCoding, NSCopying>

/**
 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 <AFURLResponseSerialization> responseSerializer;

AF通過這種協議的抽象,使得我們可以在具體應用中任意替換解析物件,如,可以替換為上面提到的AFJSONResponseSerializer,因為它正符合AFURLResponseSerialization協議

AFHTTPRequestSerializer

在AF中,對於request的序列化,同樣使用了抽象協議的方式:


@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying>

/**
 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 <AFURLRequestSerialization>

@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的靈活應用,也是值得我們借鑑的地方。