原文鏈接:http://itangqi.me/2016/05/13/the-notes-of-learning-afnetworking-four/
前言
通過前面的文章,我們已經知道 AFNetworking
是對 NSURLSession
的封裝,也瞭解它是如何發出請求的,在這裏我們對發出請求以及接收響應的過程進行序列化,這涉及到兩個模塊:
前者是處理響應的模塊,將請求返回的數據解析成對應的格式。而後者的主要作用是修改請求(主要是 HTTP 請求)的頭部,提供了一些語義明確的接口設置 HTTP 頭部字段。
我們首先會對 AFURLResponseSerialization
進行簡單的介紹,因爲這個模塊使用在 AFURLSessionManager
也就是核心類中,而後者
AFURLRequestSerialization
主要用於 AFHTTPSessionManager
中,因爲它主要用於修改 HTTP 頭部。
AFURLResponseSerialization
在瞭解模塊中類的具體實現之前,先看一下模塊的結構圖:
AFURLResponseSerialization
定義爲協議,且協議的內容非常簡單,只有一個必須實現的方法:
1 2 3 4 5 6 7 |
@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying> - (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response data:(nullable NSData *)data error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW; @end |
遵循該協議的類同時也要遵循 NSObject、NSSecureCoding 和 NSCopying 這三個協議,以實現 Objective-C 對象的基本行爲、安全編碼以及拷貝。
注:
- 模塊中的所有類都遵循
AFURLResponseSerialization
協議 AFHTTPResponseSerializer
爲模塊中最終要的根類
AFHTTPResponseSerializer
下面我們對模塊中最重要的根類,也就是 AFHTTPResponseSerializer
的實現進行分析。它是在 AFURLResponseSerialization
模塊中最基本的類(因爲
AFURLResponseSerialization
只是一個協議)
初始化
首先,依然從實例化方法入手:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
+ (instancetype)serializer { return [[self alloc] init]; } - (instancetype)init { self = [super init]; if (!self) { return nil; } self.stringEncoding = NSUTF8StringEncoding; self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)]; self.acceptableContentTypes = nil; return self; } |
因爲是對 HTTP 響應進行序列化,所以這裏設置了 stringEncoding
爲 NSUTF8StringEncoding
而且沒有對接收的內容類型加以限制。
將 acceptableStatusCodes
設置爲從 200 到 299 之間的狀態碼, 因爲只有這些狀態碼錶示獲得了有效的響應。
補充:HTTP狀態碼
驗證響應的有效性
AFHTTPResponseSerializer
中方法的實現最長,並且最重要的就是 - [AFHTTPResponseSerializer validateResponse:data:error:]
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
- (BOOL)validateResponse:(NSHTTPURLResponse *)response data:(NSData *)data error:(NSError * __autoreleasing *)error { BOOL responseIsValid = YES; NSError *validationError = nil; // 簡單的爲空判斷和類型判斷,注意如果 response 爲空或類型不對,反而 responseValid 爲 YES if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) { if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]]) { truetruetrue#1: 返回內容類型無效 } if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) { truetruetrue#2: 返回狀態碼無效 } } if (error && !responseIsValid) { *error = validationError; } return responseIsValid; } |
這個方法根據在初始化方法中初始化的屬性 acceptableContentTypes
和 acceptableStatusCodes
來判斷當前響應是否有效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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; |
其中第一、二部分的代碼非常相似,出現錯誤時通過 AFErrorWithUnderlyingError
生成格式化之後的錯誤,最後設置
responseIsValid
。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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; |
第二部分的代碼講解略。
協議的實現
主要看 AFURLResponseSerialization
協議的實現:
1 2 3 4 5 6 7 8 |
- (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { [self validateResponse:(NSHTTPURLResponse *)response data:data error:error]; return data; } |
調用上面的方法對響應進行驗證,然後返回數據,並沒有複雜的邏輯。
AFJSONResponseSerializer
接下來,看一下 AFJSONResponseSerializer
這個繼承自 AFHTTPResponseSerializer
類的實現。
初始化方法只是在調用父類的初始化方法之後更新了 acceptableContentTypes
屬性:
1 2 3 4 5 6 7 8 9 10 |
- (instancetype)init { self = [super init]; if (!self) { return nil; } self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil]; return self; } |
協議的實現
這個類中與父類差別最大的就是對 AFURLResponseSerialization
協議的實現。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
- (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { #1: 驗證請求 #2: 解決一個由只包含一個空格的響應引起的 bug, 略 #3: 序列化 JSON true #4: 移除 JSON 中的 null if (error) { *error = AFErrorWithUnderlyingError(serializationError, *error); } return responseObject; } |
-
驗證請求的有效性
1 2 3 4 5
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) { if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) { return nil; } }
-
解決一個空格引起的 bug,見 https://github.com/rails/rails/issues/1742
-
序列化 JSON
1 2 3 4 5 6 7 8 9 10
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; }
-
移除 JSON 中的 null
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
trueif (self.removesKeysWithNullValues && responseObject) { true responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions); true} true``` 其中移除 JSON 中 null 的函數 `AFJSONObjectByRemovingKeysWithNullValues` 是一個遞歸調用的函數: ```objectivec 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; }
其中移除 null
靠的就是 [mutableDictionary removeObjectForKey:key]
這一行代碼。
注:
AFXMLParserResponseSerializer
、AFXMLDocumentResponseSerializer
、AFPropertyListResponseSerializer
、 AFImageResponseSerializer 及AFCompoundResponseSerializer
將留給感興趣的同學。