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
在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 <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 解析器的實現,每個子類都有不同的實現。但它們的共同點是:
- 均繼承自AFHTTPResponseSerializer
- 都會重寫
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的一個很好的應用場景。
總結
這就是我們分析AFHTTPRequestSerializer
和AFHTTPResponseSerializer
的全部內容,關於這部分的程式碼,並不是特別複雜,只要靜下心來分析其呼叫路徑基本就能夠了解。關於AF中KVC和KVO的靈活應用,也是值得我們借鑑的地方。