AF中對接收響應(yīng)的過程進(jìn)行序列化,這涉及到AFURLResponseSerialization模塊。將請求返回的數(shù)據(jù)解析成對應(yīng)的格式。而這個模塊使用在 AFURLSessionManager
也就是核心類中。
1.模塊結(jié)構(gòu)
協(xié)議:
@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>
該協(xié)議只有一個必須實(shí)現(xiàn)的方法
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error
根類:
AFHTTPResponseSerializer遵循AFURLResponseSerialization協(xié)議。
子類:
AFJSONResponseSerializer、AFXMLParserResponseSerializer、AFXMLDocumentResponseSerializer、AFPropertyListResponseSerializer、AFImageResponseSerializer、AFCompoundResponseSerializer,即所有子類都遵循AFURLResponseSerialization協(xié)議。
2.AFHTTPResponseSerializer
這是這個模塊中最基本的類。
- 初始化
+ (instancetype)serializer {
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];//200-299的HTTP狀態(tài)碼。NSIndexSet是一個有序的,唯一的,無符號整數(shù)的集合。
self.acceptableContentTypes = nil;//沒有對接收的內(nèi)容類型加以限制
return self;
}
屬性acceptableContentTypes和acceptableStatusCodes是在初始化時給定默認(rèn)值的,我們也可以自己去定義。不在接受范圍內(nèi)的狀態(tài)碼和內(nèi)容類型會在數(shù)據(jù)解析時發(fā)生錯誤。
設(shè)置了可接受的http status code是200-299,因?yàn)橹挥羞@些狀態(tài)碼表示獲得了有效的響應(yīng)。
另外,沒有對可接受的MIME type進(jìn)行設(shè)置(交給子類來做)。在老版本的AF中,還有stringEncoding,規(guī)定utf8數(shù)據(jù)格式。
- 驗(yàn)證響應(yīng)和數(shù)據(jù)的有效性
//驗(yàn)證響應(yīng)和數(shù)據(jù)的有效性(驗(yàn)證MIMEType和status code)。子類可添加其他特定域的檢查。
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
BOOL responseIsValid = YES;//response是否合法
NSError *validationError = nil;
//response是否存在和類型判斷,如果response為空或者不是NSHTTPURLResponse類型,responseIsValid=YES!
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
//根據(jù)在初始化方法中初始化的屬性 acceptableContentTypes 和 acceptableStatusCodes 來判斷當(dāng)前響應(yīng)是否有效
//1.response的內(nèi)容類型不對(MIMEType)
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
!([response MIMEType] == nil && [data length] == 0)) {
//數(shù)據(jù)解析失敗
if ([data length] > 0 && [response URL]) {
//NSLocalizedDescriptionKey是NSError頭文件中預(yù)定義的鍵,標(biāo)識錯誤的本地化描述.可以通過NSError的localizedDescription方法獲得對應(yīng)的值信息
//NSURLErrorFailingURLErrorKey相應(yīng)的值是包含導(dǎo)致加載失敗的URL的NSURL。 此鍵僅存在于NSURLErrorDomain中。
//生成錯誤信息字典。會返回unacceptable content-type的信息,并將錯誤信息記錄在了mutableUserInfo中
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;
}
//+errorWithDomain: code: userInfo:創(chuàng)建和初始化NSError對象
//NSErrorDomain錯誤域 - 這可以是預(yù)定義的NSError域之一,也可以是描述自定義域的任意字符串。 域名不能為空。
//收到的內(nèi)容數(shù)據(jù)具有未知內(nèi)容編碼(解析數(shù)據(jù)出錯)。NSURLErrorCannotDecodeContentData = -1016,NSError錯誤碼
//出現(xiàn)錯誤時通過AFErrorWithUnderlyingError函數(shù)生成本地格式化的錯誤
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
}
responseIsValid = NO;
}
//2.狀態(tài)碼無效
if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
//-localizedStringForStatusCode:根據(jù)狀態(tài)碼獲取本地化文本內(nèi)容
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;
}
//收到從服務(wù)器來的錯誤數(shù)據(jù) NSURLErrorBadServerResponse = -1011,NSError錯誤碼
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
responseIsValid = NO;
}
}
if (error && !responseIsValid) {
*error = validationError;
}
return responseIsValid;
}
/*
1.如果content-type不滿足,那么產(chǎn)生的validationError就是Domain為AFURLResponseSerializationErrorDomain,code為NSURLErrorCannotDecodeContentData。
如果MIME type不滿足,,那么產(chǎn)生的validationError就是Domain為AFURLResponseSerializationErrorDomain,code為NSURLErrorBadServerResponse。
2.方法中,有可能會出現(xiàn)兩個錯誤,在self.acceptableContentTypes和self.acceptableStatusCodes這兩個判斷中,如果都出現(xiàn)錯誤怎么辦呢?
這就用到了NSUnderlyingErrorKey 這個字段,它表示一個優(yōu)先的錯誤,value為NSError對象。
*/
1.根據(jù)初始化中的屬性acceptableContentTypes和acceptableStatusCodes判斷響應(yīng)是否有效。
2.content-type不對,返回unacceptable content-type的信息,并將錯誤信息記錄在了mutableUserInfo中。MIME type不對,處理相似,這里不展開。
這個記錄了錯誤信息的字典,系統(tǒng)提供的KEY值:


不過也可以自定義KEY值,比如AF中就自定義了
AFNetworkingOperationFailingURLResponseErrorKey和AFNetworkingOperationFailingURLResponseDataErrorKey。
3.出現(xiàn)錯誤時通過AFErrorWithUnderlyingError函數(shù)生成本地格式化的錯誤。
3.1.如果content-type不滿足,那么產(chǎn)生的validationError就是Domain為AFURLResponseSerializationErrorDomain,code為NSURLErrorCannotDecodeContentData的自定義NSError。
3.2.如果MIME type不滿足,那么產(chǎn)生的validationError就是Domain為AFURLResponseSerializationErrorDomain,code為NSURLErrorBadServerResponse的自定義NSError。
關(guān)于NSError:NSError詳解 NSError錯誤code對照表 自定義NSError
4.如果content type和MIMEtype同時出錯,這就用到了NSUnderlyingErrorKey這個字段,它表示一個優(yōu)先的錯誤,value為NSError對象。
具體看下面這個函數(shù):
//生成本地格式化的錯誤。填充錯誤信息,一些處理過程中產(chǎn)生的錯誤信息填充到我們需要返回給用戶的自定義錯誤中
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
//NSUnderlyingErrorKey表示優(yōu)先錯誤
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];
}
在兩者都出錯的情況下,那么UnderlyingError就是content type error。
- 協(xié)議的實(shí)現(xiàn)
1.AFURLResponseSerialization協(xié)議:
//從與指定響應(yīng)相關(guān)聯(lián)的數(shù)據(jù)中decode得到的響應(yīng)對象。
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error{
//調(diào)用驗(yàn)證方法,返回data
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
return data;
}
把驗(yàn)證方法調(diào)用完之后就返回data,沒有其他實(shí)現(xiàn)了。
2.NSSecureCoding、NSCopying協(xié)議:一些歸檔和自定義copy的方法,比較常規(guī)的寫法,不做說明了。
3.AFJSONResponseSerializer
可接受的數(shù)據(jù)類型:application/json,text/json,text/javascript。
- 協(xié)議的實(shí)現(xiàn)
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
//驗(yàn)證MIMEType和status code
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
//error為空或者 錯誤、優(yōu)先錯誤匹配error code和domain(在這里是content type類型的錯誤)
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
//數(shù)據(jù)是否是一個空格
BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
//如果數(shù)據(jù)為空或者是空格,就不json解析
if (data.length == 0 || isSpace) {
return nil;
}
NSError *serializationError = nil;
//json解析。NSJSON只支持解析UTF8編碼的數(shù)據(jù)
id responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
if (!responseObject)
{
if (error) {
//用json解析的error去填充錯誤信息
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return nil;
}
//是否要從響應(yīng)的JSON數(shù)據(jù)中刪除帶有“NSNull”值的鍵
if (self.removesKeysWithNullValues) {
return AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
return responseObject;
}
1.驗(yàn)證響應(yīng)
驗(yàn)證失敗。在沒有error 或者 錯誤中的code是NSURLErrorCannotDecodeContentData(即content type不匹配)的情況下,是不能解析數(shù)據(jù)的,就返回nil。用到的函數(shù):
// 檢測錯誤或者優(yōu)先錯誤中是否匹配code和domain
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
//判斷錯誤域和傳過來的域名是否一致,錯誤code是否一致
if ([error.domain isEqualToString:domain] && error.code == code) {
return YES;
} else if (error.userInfo[NSUnderlyingErrorKey]) {//如果NSUnderlyingErrorKey對應(yīng)有值,就再進(jìn)行判斷
return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
}
return NO;
}
2.處理返回的數(shù)據(jù)中只有空格的情況
如果數(shù)據(jù)為空或者只有一個空格,就不解析。
3.解析JSON
readingOptions屬性設(shè)置json的讀取選項(xiàng)。這里的默認(rèn)值是NSJSONReadingMutableContainers
typedef NS_OPTIONS(NSUInteger, NSJSONReadingOptions) {
//返回可變?nèi)萜?,NSMutableDictionary或NSMutableArray
NSJSONReadingMutableContainers = (1UL << 0),
//返回的JSON對象中字符串的值為NSMutableString
NSJSONReadingMutableLeaves = (1UL << 1),
//允許JSON字符串最外層既不是NSArray也不是NSDictionary,但必須是有效的JSON Fragment。例如使用這個選項(xiàng)可以解析 @“123” 這樣的字符串。
NSJSONReadingAllowFragments = (1UL << 2)
} NS_ENUM_AVAILABLE(10_7, 5_0);
傳送門- JSON解析 NSJSONReadingMutableContainers的作用
4.是否要從響應(yīng)的JSON數(shù)據(jù)中刪除帶有“NSNull”值的鍵
用到的函數(shù):主要通過遞歸的手段來實(shí)現(xiàn)的。
//從響應(yīng)的JSON數(shù)據(jù)中刪除帶有“NSNull”值的鍵
static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
//數(shù)組
if ([JSONObject isKindOfClass:[NSArray class]]) {
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
//遍歷數(shù)組,通過遞歸的手段清空數(shù)組內(nèi)的null
for (id value in (NSArray *)JSONObject) {
[mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
}
//按位與操作,解析類型是否NSJSONReadingMutableContainers(mutableArray或者mutabledictionary)
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;
}
4.AFXMLParserResponseSerializer、AFXMLDocumentResponseSerializer、AFPropertyListResponseSerializer
AFXMLParserResponseSerializer用來解析XML數(shù)據(jù),支持的ContentType:application/xml、text/xml。
AFXMLDocumentResponseSerializer同上,但這個類只能在mac os x上使用。
AFPropertyListResponseSerializer用來解析plist數(shù)據(jù),支持的ContentType:application/x-plist。
這三個子類的實(shí)現(xiàn)和上面JSON子類的實(shí)現(xiàn)差不多,就不具體展開了。
5.AFImageResponseSerializer
用于驗(yàn)證和解碼圖像響應(yīng)。
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
//驗(yàn)證MIME type和status code
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
//圖片解壓。宏判斷是那種設(shè)備,進(jìn)行對應(yīng)的圖片解壓處理
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
if (self.automaticallyInflatesResponseImage) {//是否對響應(yīng)的圖片進(jìn)行自動處理
return AFInflatedImageFromResponseWithDataAtScale((NSHTTPURLResponse *)response, data, self.imageScale);
} else {
return AFImageWithDataAtScale(data, self.imageScale);
}
#else
// Ensure that the image is set to it's correct pixel width and height
NSBitmapImageRep *bitimage = [[NSBitmapImageRep alloc] initWithData:data];
NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize([bitimage pixelsWide], [bitimage pixelsHigh])];
[image addRepresentation:bitimage];
return image;
#endif
return nil;
}
在這里用到了幾個方法,寫在了UIImage分類UIImage (AFNetworkingSafeImageLoading)里面。下面來看一下分類中的這幾個方法:
1.把NSData安全地轉(zhuǎn)換為UIImage。
+ (UIImage *)af_safeImageWithData:(NSData *)data {
UIImage* image = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
imageLock = [[NSLock alloc] init];
});
[imageLock lock];//上鎖
image = [UIImage imageWithData:data];
[imageLock unlock];//開鎖
return image;
}
當(dāng)我們讀寫一個數(shù)據(jù)的時候,由于數(shù)據(jù)還可能被別人讀寫,這就有可能出現(xiàn)不安全的情況,為了解決這個問題,就使用了“鎖”。如果對線程鎖比較熟悉的話就容易理解了,簡單說呢,就是在寫數(shù)據(jù)前先上鎖,那么別人就無法使用這塊數(shù)據(jù)了,直到你執(zhí)行完數(shù)據(jù)操作、解鎖。
2.私有函數(shù),按照scale對圖片進(jìn)行伸縮處理
//返回一個按照scale收縮的圖片
static UIImage * AFImageWithDataAtScale(NSData *data, CGFloat scale) {
UIImage *image = [UIImage af_safeImageWithData:data];
if (image.images) {//gif圖不需要伸縮
return image;
}
return [[UIImage alloc] initWithCGImage:[image CGImage] scale:scale orientation:image.imageOrientation];
}
關(guān)于image.images:這個屬性第一次接觸,大致看了一下,常見的應(yīng)用是用來生成一個gif效果。在gif圖中表示這個Gif包含了多少張圖片。其余用法還沒有深入研究。
3.根據(jù)響應(yīng)結(jié)果和scale返回一張圖片.完成圖像解壓工作
static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale)
這個函數(shù)實(shí)現(xiàn)很長,用到了CoreGraphics上的一些東西,主要完成iOS、TV、Watch設(shè)備下的圖像解壓工作。關(guān)于圖像解壓的目的,我在這篇文章中讀到這么一段話:
當(dāng)我們調(diào)用UIImage的方法imageWithData:方法把數(shù)據(jù)轉(zhuǎn)成UIImage對象后,其實(shí)這時UIImage對象還沒準(zhǔn)備好需要渲染到屏幕的數(shù)據(jù),現(xiàn)在的網(wǎng)絡(luò)圖像PNG和JPG都是壓縮格式,需要把它們解壓轉(zhuǎn)成bitmap后才能渲染到屏幕上,如果不做任何處理,當(dāng)你把UIImage賦給UIImageView,在渲染之前底層會判斷到UIImage對象未解壓,沒有bitmap數(shù)據(jù),這時會在主線程對圖片進(jìn)行解壓操作,再渲染到屏幕上。這個解壓操作是比較耗時的,如果任由它在主線程做,可能會導(dǎo)致速度慢UI卡頓的問題。
AFImageResponseSerializer除了把返回數(shù)據(jù)解析成UIImage外,還會把圖像數(shù)據(jù)解壓,這個處理是在子線程(AFNetworking專用的一條線程,詳見AFURLConnectionOperation),處理后上層使用返回的UIImage在主線程渲染時就不需要做解壓這步操作,主線程減輕了負(fù)擔(dān),減少了UI卡頓問題。
具體實(shí)現(xiàn)上在AFInflatedImageFromResponseWithDataAtScale里,創(chuàng)建一個畫布,把UIImage畫在畫布上,再把這個畫布保存成UIImage返回給上層。只有JPG和PNG才會嘗試去做解壓操作,期間如果解壓失敗,或者遇到CMKY顏色格式的jpg,或者圖像太大(解壓后的bitmap太占內(nèi)存,一個像素3-4字節(jié),搞不好內(nèi)存就爆掉了),就直接返回未解壓的圖像。
另外在代碼里看到iOS才需要這樣手動解壓,MacOS上已經(jīng)有封裝好的對象NSBitmapImageRep可以做這個事。
6.AFCompoundResponseSerializer
這是一個對復(fù)合類型的響應(yīng)進(jìn)行處理的子類。
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
//遍歷數(shù)組,只要是屬于AFHTTPResponseSerializer及其子類的類型,就執(zhí)行相應(yīng)的響應(yīng)操作。
for (id <AFURLResponseSerialization> serializer in self.responseSerializers) {
if (![serializer isKindOfClass:[AFHTTPResponseSerializer class]]) {
continue;
}
NSError *serializerError = nil;
id responseObject = [serializer responseObjectForResponse:response data:data error:&serializerError];
if (responseObject) {
if (error) {
*error = AFErrorWithUnderlyingError(serializerError, *error);
}
return responseObject;
}
}
//以上類型都不是,就執(zhí)行默認(rèn)響應(yīng)操作。
return [super responseObjectForResponse:response data:data error:error];//調(diào)用父類方法
}
responseSerializers屬性,這個數(shù)組中裝著多種序列化類型,比如上面講到的JSON、XML等等。
遍歷數(shù)組,只要是屬于AFHTTPResponseSerializer及其子類的類型,就執(zhí)行相應(yīng)的響應(yīng)操作。如果以上類型都不是,就執(zhí)行默認(rèn)的響應(yīng)。
詳細(xì)源碼注釋請戳:https://github.com/huixinHu/AFNetworking-
參考文章
AFNetworking源碼閱讀(五)
AFNetworking2.0源碼解析AFURLResponseSerialization