版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2018.03.01 |
前言
我們做APP發(fā)起網(wǎng)絡(luò)請求,都離不開一個非常有用的框架AFNetworking,可以說這個框架的知名度已經(jīng)超過了蘋果的底層網(wǎng)絡(luò)請求部分,很多人可能不知道蘋果底層是如何發(fā)起網(wǎng)絡(luò)請求的,但是一定知道
AFNetworking
,接下來幾篇我們就一起詳細(xì)的解析一下這個框架。感興趣的可以看上面寫的幾篇。
1. AFNetworking源碼探究(一) —— 基本介紹
2. AFNetworking源碼探究(二) —— GET請求實現(xiàn)之NSURLSessionDataTask實例化(一)
3. AFNetworking源碼探究(三) —— GET請求實現(xiàn)之任務(wù)進(jìn)度設(shè)置和通知監(jiān)聽(一)
4. AFNetworking源碼探究(四) —— GET請求實現(xiàn)之代理轉(zhuǎn)發(fā)思想(一)
5. AFNetworking源碼探究(五) —— AFURLSessionManager中NSURLSessionDelegate詳細(xì)解析(一)
6. AFNetworking源碼探究(六) —— AFURLSessionManager中NSURLSessionTaskDelegate詳細(xì)解析(一)
7. AFNetworking源碼探究(七) —— AFURLSessionManager中NSURLSessionDataDelegate詳細(xì)解析(一)
8. AFNetworking源碼探究(八) —— AFURLSessionManager中NSURLSessionDownloadDelegate詳細(xì)解析(一)
9. AFNetworking源碼探究(九) —— AFURLSessionManagerTaskDelegate中三個轉(zhuǎn)發(fā)代理方法詳細(xì)解析(一)
10. AFNetworking源碼探究(十) —— 數(shù)據(jù)解析之?dāng)?shù)據(jù)解析架構(gòu)的分析(一)
回顧
上一篇我們主要介紹了有關(guān)數(shù)據(jù)解析類和協(xié)議,以及實現(xiàn)解析的架構(gòu),這一篇就分開講述各個類是如何實現(xiàn)對應(yīng)的數(shù)據(jù)解析的。
AFURLResponseSerialization協(xié)議
我們先看一下這個協(xié)議的接口
/**
The `AFURLResponseSerialization` protocol is adopted by an object that decodes data into a more useful object representation, according to details in the server response. Response serializers may additionally perform validation on the incoming response and data.
For example, a JSON response serializer may check for an acceptable status code (`2XX` range) and content type (`application/json`), decoding a valid JSON response into an object.
*/
@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
根據(jù)服務(wù)器響應(yīng)中的細(xì)節(jié),AFURLResponseSerialization
協(xié)議被一個對象采用,該對象將數(shù)據(jù)解碼為更有用的對象表示。 Response序列化器還可以對傳入響應(yīng)和數(shù)據(jù)執(zhí)行驗證。例如,JSON響應(yīng)序列化器可以檢查可接受的狀態(tài)碼(2XX
范圍)和內(nèi)容類型(application / json
),將有效的JSON響應(yīng)解碼成對象
AFHTTPResponseSerializer
這個是所有其他解析類的父類,他遵守上面的AFURLResponseSerialization
協(xié)議。
我們看一下協(xié)議在這個類中的實現(xiàn)
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
return data;
}
這里調(diào)用了一個方法,進(jìn)行了指定response和數(shù)據(jù)的驗證。
/**
Validates the specified response and data.
In its base implementation, this method checks for an acceptable status code and content type. Subclasses may wish to add other domain-specific checks.
@param response The response to be validated.
@param data The data associated with the response.
@param error The error that occurred while attempting to validate the response.
@return `YES` if the response is valid, otherwise `NO`.
*/
- (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error;
在其基本實現(xiàn)中,此方法檢查可接受的狀態(tài)碼和內(nèi)容類型。 子類可能希望添加其他域特定的檢查。
下面我們看一下驗證過程,主要對應(yīng)下面這段代碼
- (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;
}
這是一個具有返回值類型為BOOL的方法,但是這里對于返回值并沒有使用。
(a) 最外層的判斷
最外層的判斷主要是
if (response && [response isKindOfClass:[NSHTTPURLResponse class]])
就是如果response不是nil,并且response的類型是NSHTTPURLResponse
。
(b) 第一個if判斷
在上面最外層判斷的內(nèi)部是兩個if判斷,根據(jù)不同的條件判斷數(shù)據(jù)是否有效以及在無效時應(yīng)該拋出怎樣的異常。
主要對應(yīng)下面這段代碼
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;
}
從responseIsValid = NO
,我們可以看出來,這一定是拋出異常,沒有驗證通過的,但是為什么拋出異常呢?我們看一下。
如果有接受數(shù)據(jù)類型,如果不匹配response,而且響應(yīng)類型不為空,數(shù)據(jù)長度不為0。接著進(jìn)行判斷,如果數(shù)據(jù)長度大于0,而且有響應(yīng)URL,那么就生成mutableUserInfo
信息,調(diào)用下面的方法生成錯誤信息。
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
if (!error) {
return underlyingError;
}
if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
return error;
}
NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;
return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}
這里要注意,NSURLResponse中這個MIMEType屬性。
/*!
@abstract Returns the MIME type of the receiver.
@discussion The MIME type is based on the information provided
from an origin source. However, that value may be changed or
corrected by a protocol implementation if it can be determined
that the origin server or source reported the information
incorrectly or imprecisely. An attempt to guess the MIME type may
be made if the origin source did not report any such information.
@result The MIME type of the receiver.
@abstract返回接收者的MIME類型。
@討論MIME類型基于提供的信息
來源。 但是,該值可能會改變或
如果可以確定原始服務(wù)器或來源報告了信息
不正確或不準(zhǔn)確,則由協(xié)議實施糾正
。如果原始資料來源未報告任何此類信息,
可以嘗試猜測MIME類型
@result接收者的MIME類型。
*/
@property (nullable, readonly, copy) NSString *MIMEType;
(c) 第二個if判斷
主要對應(yīng)下邊這段代碼
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;
}
判斷自己可接受的狀態(tài)碼,如果和response的狀態(tài)碼不匹配,則進(jìn)入if塊,生成錯誤和標(biāo)識。
(d) error和responseIsValid判斷
主要是下面一段代碼
if (error && !responseIsValid) {
*error = validationError;
}
這里,如果error不為空,并且responseIsValid == NO,也就是說上面兩個if判斷至少走過了一個,這時候給error進(jìn)行了賦值。
*error = validationError;
這個方法就是來判斷返回數(shù)據(jù)與咱們使用的解析器是否匹配,需要解析的狀態(tài)碼是否匹配。
兩個屬性值,一個acceptableContentTypes
,一個acceptableStatusCodes
,兩者在初始化的時候有給默認(rèn)值,如果給acceptableContentTypes
定義了不匹配的類型,那么數(shù)據(jù)仍舊會解析錯誤。
AFJSONResponseSerializer
AFJSONResponseSerializer
是AFHTTPResponseSerializer
的一個子類,用于驗證和解碼JSON響應(yīng)。
默認(rèn)情況下,AFJSONResponseSerializer
接受以下MIME類型,其中包括官方標(biāo)準(zhǔn),application / json
以及其他常用類型:
application / json
text / json
text / javascript
我們看一下協(xié)議在這個類中的實現(xiàn)
- (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;
}
}
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;
}
if (self.removesKeysWithNullValues && responseObject) {
responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return responseObject;
}
下面就看一下,這里都做了a什么
(a) 有效性的驗證
我們看一下如何進(jìn)行有效性的驗證的。
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
就是調(diào)用我們上面解析的,驗證有效性的方法。如果無效,進(jìn)入判斷,接著if判斷,如果error為空,或者有錯誤,去函數(shù)里判斷。
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
if ([error.domain isEqualToString:domain] && error.code == code) {
return YES;
} else if (error.userInfo[NSUnderlyingErrorKey]) {
return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
}
return NO;
}
數(shù)據(jù)無效后,返回nil。
(b) 幾個條件判斷
下面就是幾個條件判斷,滿足的話直接序列化對應(yīng)的JSON數(shù)據(jù),不滿足的話返回nil。
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;
}
if (self.removesKeysWithNullValues && responseObject) {
responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
- 第一組條件判斷
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;
}
這里首先判斷數(shù)據(jù)是否為空,利用isEqualToData:
方法進(jìn)行判斷,如果不為空,并且數(shù)據(jù)長度大于0,那么就進(jìn)行JSON數(shù)據(jù)的序列化。
responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
如果不滿足上面條件就返nil。
- 第二組條件判斷
if (self.removesKeysWithNullValues && responseObject) {
responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
這里有一個屬性
/**
Whether to remove keys with `NSNull` values from response JSON. Defaults to `NO`.
*/
@property (nonatomic, assign) BOOL removesKeysWithNullValues;
是否從響應(yīng)JSON中刪除具有NSNull
值的鍵。 默認(rèn)為NO。如果需要移除這個鍵并且上面的responseObject已經(jīng)序列化成功,那么就要調(diào)用下面的函數(shù)移除具有NSNull
值的鍵。
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;
}
這里有一個屬性和枚舉,一起來看一下
/**
Options for reading the response JSON data and creating the Foundation objects. For possible values, see the `NSJSONSerialization` documentation section "NSJSONReadingOptions". `0` by default.
*/
用于讀取響應(yīng)JSON數(shù)據(jù)并創(chuàng)建Foundation對象的選項。 有關(guān)可能的值,請參閱“NSJSONSerialization”文檔部分“NSJSONReadingOptions”。 默認(rèn)為'0'
@property (nonatomic, assign) NSJSONReadingOptions readingOptions;
typedef NS_OPTIONS(NSUInteger, NSJSONReadingOptions) {
NSJSONReadingMutableContainers = (1UL << 0),
NSJSONReadingMutableLeaves = (1UL << 1),
NSJSONReadingAllowFragments = (1UL << 2)
} API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- 第三組條件判斷
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
如果error不為空,那么就利用函數(shù)AFErrorWithUnderlyingError
生成NSError對象并賦值。
后記
本篇講述了一個
AFURLResponseSerialization
協(xié)議以及AFHTTPResponseSerializer
和AFJSONResponseSerializer
類中父類那個協(xié)議方法的實現(xiàn)。