該文章閱讀的AFNetworking的版本為3.2.0。
AFURLResponseSerialization
這個類是用來解析伺服器返回的資料,根據伺服器返回資料的不同,解析資料的類也不同。
1.一個協議
AFURLResponseSerialization
這個協議只定義了一個方法,這個方法將伺服器返回的資料解析後進行返回。
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
複製程式碼
2.七個類
這七個類又分為了一個基類和六個子類,先來看基類。
2.1 AFHTTPResponseSerializer類
這個類是其他六個類的基類。
2.1.1 介面部分
2.1.1.1 屬性
/**
字串編碼方式,預設為NSUTF8StringEncoding
*/
@property (nonatomic, assign) NSStringEncoding stringEncoding;
/**
可接受的狀態碼集合,如果返回的狀態碼不在集合中則會驗證時報錯
*/
@property (nonatomic, copy, nullable) NSIndexSet *acceptableStatusCodes;
/**
可接受的Content-Type集合,如果返回的型別不在集合中則會驗證時報錯
*/
@property (nonatomic, copy, nullable) NSSet <NSString *> *acceptableContentTypes;
複製程式碼
2.1.1.2 方法
/**
例項化方法
*/
+ (instancetype)serializer;
/**
初始化方法
*/
- (instancetype)init;
/**
驗證伺服器返回的資料
*/
- (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error;
複製程式碼
2.1.2 實現部分
+ (instancetype)serializer {
// 普通的例項化方法
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
// 初始化屬性
self.stringEncoding = NSUTF8StringEncoding;
// 可接受的狀態碼為200~299之間
self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
self.acceptableContentTypes = nil;
return self;
}
#pragma mark -
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
// 設定臨時變數
BOOL responseIsValid = YES;
NSError *validationError = nil;
// 如果response有值,並且response是NSHTTPURLResponse型別的物件
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
// 如果設定了可接受的ContentTypes,並且響應的content-type不在可接受的範圍內,並且響應的content-type有值或者返回了資料
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
!([response MIMEType] == nil && [data length] == 0)) {
// 如果返回了資料,並且響應有URL
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;
}
// 如果設定了可接受狀態碼,並且響應的狀態碼不在可接受的範圍內,並且響應有URL
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;
}
}
// 如果傳了NSError物件,並且響應無效,就賦值後返回錯誤資訊
if (error && !responseIsValid) {
*error = validationError;
}
return responseIsValid;
}
複製程式碼
2.1.3 AFURLResponseSerialization協議方法的實現
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
// 作為基類,僅僅實現了對響應和資料的驗證
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
return data;
}
複製程式碼
2.1.4 NSSecureCoding協議方法的實現
+ (BOOL)supportsSecureCoding {
// 如果一個類符合 NSSecureCoding 協議並在 + supportsSecureCoding 返回 YES,就宣告瞭它可以處理本身例項的編碼解碼方式,以防止替換攻擊。
return YES;
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [self init];
if (!self) {
return nil;
}
self.acceptableStatusCodes = [decoder decodeObjectOfClass:[NSIndexSet class] forKey:NSStringFromSelector(@selector(acceptableStatusCodes))];
self.acceptableContentTypes = [decoder decodeObjectOfClass:[NSIndexSet class] forKey:NSStringFromSelector(@selector(acceptableContentTypes))];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.acceptableStatusCodes forKey:NSStringFromSelector(@selector(acceptableStatusCodes))];
[coder encodeObject:self.acceptableContentTypes forKey:NSStringFromSelector(@selector(acceptableContentTypes))];
}
複製程式碼
2.1.5 NSCopying協議方法的實現
- (instancetype)copyWithZone:(NSZone *)zone {
AFHTTPResponseSerializer *serializer = [[[self class] allocWithZone:zone] init];
serializer.acceptableStatusCodes = [self.acceptableStatusCodes copyWithZone:zone];
serializer.acceptableContentTypes = [self.acceptableContentTypes copyWithZone:zone];
return serializer;
}
複製程式碼
2.2 AFJSONResponseSerializer類
通過這個類的命名可以直觀的看出這個類是用來解析返回資料為JSON格式的。
2.2.1 介面部分
2.2.1.1 屬性
/**
json資料解析選項,預設是0
*/
@property (nonatomic, assign) NSJSONReadingOptions readingOptions;
/**
是否刪除value為NSNull的key,預設是否
*/
@property (nonatomic, assign) BOOL removesKeysWithNullValues;
複製程式碼
2.2.1.2 方法
/**
初始化方法
*/
- (instancetype)init;
/**
指定json解析選項的例項化工廠方法
*/
+ (instancetype)serializerWithReadingOptions:(NSJSONReadingOptions)readingOptions;
複製程式碼
2.2.2 實現部分
+ (instancetype)serializer {
// 重寫父類方法,以不選擇json解析選項的方式呼叫了自己的例項化工廠方法
return [self serializerWithReadingOptions:(NSJSONReadingOptions)0];
}
+ (instancetype)serializerWithReadingOptions:(NSJSONReadingOptions)readingOptions {
// 例項化本類物件指定json解析選項並返回
AFJSONResponseSerializer *serializer = [[self alloc] init];
serializer.readingOptions = readingOptions;
return serializer;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
// 設定了可接受的Content-Type
self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
return self;
}
複製程式碼
2.2.3 AFURLResponseSerialization協議方法的實現
- (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;
}
}
// 為了解決在Safari上的一個bug,如果返回的資料為空或者是一個空格就直接返回
// 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) {
// 就將傳入的錯誤資訊新增到serializationError的NSUnderlyingErrorKey欄位中
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return nil;
}
// 如果想要過濾NSNull,就進行過濾
if (self.removesKeysWithNullValues) {
return AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
return responseObject;
}
複製程式碼
2.2.4 NSSecureCoding協議方法的實現
同2.1.4
2.2.5 NSCopying協議方法的實現
同2.1.5
2.3 AFXMLParserResponseSerializer類
當伺服器返回的資料型別為XML時,用這個類進行解析,但這個類只返回NSXMLParser物件,具體的解析還要通過實現代理手動解析
2.3.1 介面部分
介面部分沒有申明新的屬性和方法
2.3.2 實現部分
+ (instancetype)serializer {
// 例項化物件
AFXMLParserResponseSerializer *serializer = [[self alloc] init];
return serializer;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
// 設定了可接受的Content-Type
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/xml", @"text/xml", nil];
return self;
}
複製程式碼
2.3.3 AFURLResponseSerialization協議方法的實現
- (id)responseObjectForResponse:(NSHTTPURLResponse *)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;
}
}
// 返回NSXMLParser物件
return [[NSXMLParser alloc] initWithData:data];
}
複製程式碼
2.4 AFXMLDocumentResponseSerializer類
這個類同樣是用來解析當伺服器返回的資料型別為XML,但是這個類用在MAC上,同樣也是隻返回NSXMLDocument物件,具體要手動解析
2.4.1 介面部分
2.4.1.1 屬性
/**
NSXMLDocument物件輸入輸出的選項,預設為0
*/
@property (nonatomic, assign) NSUInteger options;
複製程式碼
2.4.1.2 方法
/**
初始化方法
*/
- (instancetype)init;
/**
例項化物件的工廠方法
*/
+ (instancetype)serializerWithXMLDocumentOptions:(NSUInteger)mask;
複製程式碼
2.4.2 實現部分
+ (instancetype)serializer {
// 呼叫本類的例項化工廠方法
return [self serializerWithXMLDocumentOptions:0];
}
+ (instancetype)serializerWithXMLDocumentOptions:(NSUInteger)mask {
// 例項化物件並設定引數
AFXMLDocumentResponseSerializer *serializer = [[self alloc] init];
serializer.options = mask;
// 返回物件
return serializer;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
// 設定了可接受的Content-Type
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/xml", @"text/xml", nil];
return self;
}
複製程式碼
2.4.3 AFURLResponseSerialization協議方法的實現
- (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;
}
}
// 例項化NSXMLDocument物件
NSError *serializationError = nil;
NSXMLDocument *document = [[NSXMLDocument alloc] initWithData:data options:self.options error:&serializationError];
// 如果例項化沒有成功就直接返回
if (!document)
{
// 如果傳入了錯誤資訊引數
if (error) {
// 就將傳入的錯誤資訊新增到serializationError的NSUnderlyingErrorKey欄位中
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return nil;
}
return document;
}
複製程式碼
2.4.4 NSSecureCoding協議方法的實現
同2.1.4
2.4.5 NSCopying協議方法的實現
同2.1.5
2.5 AFPropertyListResponseSerializer類
這個類是用來將伺服器返回的資料解析為plist型別
2.5.1 介面部分
2.5.1.1 屬性
/**
指定返回的plist格式,但即使設定了也沒有用,因為在實現中並沒用用到這個引數而是直接設定為NULL
*/
@property (nonatomic, assign) NSPropertyListFormat format;
/**
建立plist的可變選項
*/
@property (nonatomic, assign) NSPropertyListReadOptions readOptions;
複製程式碼
2.5.1.2 方法
/**
初始化方法
*/
- (instancetype)init;
/**
例項化物件的工廠方法
*/
+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
readOptions:(NSPropertyListReadOptions)readOptions;
複製程式碼
2.5.2 實現部分
+ (instancetype)serializer {
// 呼叫本類的例項化工廠方法
return [self serializerWithFormat:NSPropertyListXMLFormat_v1_0 readOptions:0];
}
+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
readOptions:(NSPropertyListReadOptions)readOptions
{
// 例項化物件並設定引數
AFPropertyListResponseSerializer *serializer = [[self alloc] init];
serializer.format = format;
serializer.readOptions = readOptions;
return serializer;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
// 設定了可接受的Content-Type
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/x-plist", nil];
return self;
}
複製程式碼
2.5.3 AFURLResponseSerialization協議方法的實現
- (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;
}
}
// 如果沒有資料就直接返回
if (!data) {
return nil;
}
// 例項化NSPropertyListSerialization物件解析data
NSError *serializationError = nil;
id responseObject = [NSPropertyListSerialization propertyListWithData:data options:self.readOptions format:NULL error:&serializationError];
// 如果沒有解析成功就直接返回
if (!responseObject)
{
if (error) {
// 就將傳入的錯誤資訊新增到serializationError的NSUnderlyingErrorKey欄位中
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return nil;
}
return responseObject;
}
複製程式碼
2.5.4 NSSecureCoding協議方法的實現
同2.1.4
2.5.5 NSCopying協議方法的實現
同2.1.5
2.6 AFImageResponseSerializer類
這個類是用來解析伺服器返回的圖片資料的
2.6.1 介面部分
2.6.1.1 屬性
/**
圖片的縮放因子,預設為螢幕的縮放因子
*/
@property (nonatomic, assign) CGFloat imageScale;
/**
是否對伺服器返回的圖片進行自動解壓,預設為自動解壓
*/
@property (nonatomic, assign) BOOL automaticallyInflatesResponseImage;
複製程式碼
2.6.2 實現部分
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
// 設定了自身的屬性
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"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", nil];
#if TARGET_OS_IOS || TARGET_OS_TV
self.imageScale = [[UIScreen mainScreen] scale];
self.automaticallyInflatesResponseImage = YES;
#elif TARGET_OS_WATCH
self.imageScale = [[WKInterfaceDevice currentDevice] screenScale];
self.automaticallyInflatesResponseImage = YES;
#endif
return self;
}
複製程式碼
2.6.3 AFURLResponseSerialization協議方法的實現
- (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;
}
}
// 在移動端需要手動解壓
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
// 如果設定了自動解壓則進行解壓,否則就直接生成
if (self.automaticallyInflatesResponseImage) {
return AFInflatedImageFromResponseWithDataAtScale((NSHTTPURLResponse *)response, data, self.imageScale);
} else {
return AFImageWithDataAtScale(data, self.imageScale);
}
// 在MAC上可以直接呼叫系統方法解壓
#else
// Ensure that the image is set to it is 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;
}
複製程式碼
2.6.4 NSSecureCoding協議方法的實現
同2.1.4
2.6.5 NSCopying協議方法的實現
同2.1.5
2.7 AFCompoundResponseSerializer類
這個類可以用來解析多個不同型別的資料型別
2.7.1 介面部分
2.7.1.1 屬性
/**
響應解析物件陣列
*/
@property (readonly, nonatomic, copy) NSArray <id<AFURLResponseSerialization>> *responseSerializers;
複製程式碼
2.7.1.2 方法
/**
例項化工廠方法
*/
+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray <id<AFURLResponseSerialization>> *)responseSerializers;
複製程式碼
2.7.2 類擴充套件
/**
類擴充套件中只有一個屬性用於儲存傳入的響應解物件陣列
*/
@property (readwrite, nonatomic, copy) NSArray *responseSerializers;
複製程式碼
2.7.3 實現部分
+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray *)responseSerializers {
// 例項化本類物件並設定屬性
AFCompoundResponseSerializer *serializer = [[self alloc] init];
serializer.responseSerializers = responseSerializers;
return serializer;
}
複製程式碼
2.7.4 AFURLResponseSerialization協議方法的實現
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
// 遍歷傳入的響應解析物件陣列
for (id <AFURLResponseSerialization> serializer in self.responseSerializers) {
// 如果不是AFHTTPResponseSerializer類或者其子類就跳過進行下一個
if (![serializer isKindOfClass:[AFHTTPResponseSerializer class]]) {
continue;
}
// 呼叫其對應的解析方法,解析成功則返回
NSError *serializerError = nil;
id responseObject = [serializer responseObjectForResponse:response data:data error:&serializerError];
if (responseObject) {
// 如果有錯誤就將傳入的錯誤資訊新增到serializationError的NSUnderlyingErrorKey欄位中
if (error) {
*error = AFErrorWithUnderlyingError(serializerError, *error);
}
return responseObject;
}
}
// 如果傳入的響應解析物件都解析不了就呼叫其父類進行解析
return [super responseObjectForResponse:response data:data error:error];
}
複製程式碼
2.7.5 NSSecureCoding協議方法的實現
同2.1.4
2.6.6 NSCopying協議方法的實現
同2.1.5
3.三個全域性靜態常量
3.1 介面宣告
/**
用來識別是AFURLResponseSerialization物件解析的錯誤
*/
FOUNDATION_EXPORT NSString * const AFURLResponseSerializationErrorDomain;
/**
用來識別是AFURLResponseSerialization物件解析錯誤中的伺服器返回的NSURLResponse物件
*/
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseErrorKey;
/**
用來識別是AFURLResponseSerialization物件解析錯誤中的伺服器返回的NSData物件
*/
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey;
複製程式碼
3.2 私有實現
NSString * const AFURLResponseSerializationErrorDomain = @"com.alamofire.error.serialization.response";
NSString * const AFNetworkingOperationFailingURLResponseErrorKey = @"com.alamofire.serialization.response.error.response";
NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey = @"com.alamofire.serialization.response.error.data";
複製程式碼
4.五個私有靜態方法
/**
該方法用於將一個NSError物件作為另一個NSError物件的附屬NSError物件,並將其放到userInfo屬性NSUnderlyingErrorKey鍵對應的值中
*/
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
// 沒有error為空的話就直接返回underlyingError
if (!error) {
return underlyingError;
}
// 如果underlyingError為空或者error中已經有附屬underlyingError就直接返回error
if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
return error;
}
// 獲取error的userInfo,並將underlyingError作為鍵NSUnderlyingErrorKey對應的值賦給error
NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;
return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}
/**
該方法用於判斷error或者underlyingError是否為指定code和domain的erro
*/
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
// 如果error符合指定條件則返回YES
if ([error.domain isEqualToString:domain] && error.code == code) {
return YES;
// 如果error不符合但是有underlyingError,則遞迴呼叫本方法
} else if (error.userInfo[NSUnderlyingErrorKey]) {
return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
}
// 如果都不滿足就返回NO
return NO;
}
/**
該方法用於過濾解析後的json資料中NSDictionary型別資料值為Null的鍵
*/
static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
// 如果解析後的json資料是NSArray型別的
if ([JSONObject isKindOfClass:[NSArray class]]) {
// 遍歷元素如果還是NSArray型別的就遞迴呼叫本方法直至元素不為NSArray型別
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
for (id value in (NSArray *)JSONObject) {
[mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
}
// 根據傳入引數的可變性來返回對應可變性的NSArray物件
return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
// 如果解析後的json資料是NSDictionary型別的
} else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
// 遍歷所有的key並取出對應的value
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
id value = (NSDictionary *)JSONObject[key];
// 如果value不存在或者是NSNull型別的物件,就將其移除
if (!value || [value isEqual:[NSNull null]]) {
[mutableDictionary removeObjectForKey:key];
// 如果value是NSArray或者NSDictionary型別的物件就遞迴呼叫本方法直至元素不為NSArray或者NSDictionary型別的物件
} else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
}
}
// 根據傳入引數的可變性來返回對應可變性的NSDictionary物件
return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
}
// 如果不是是NSArray或者NSDictionary型別的物件就直接返回
return JSONObject;
}
/**
生成對應縮放因子的圖片
*/
static UIImage * AFImageWithDataAtScale(NSData *data, CGFloat scale) {
// 根據二進位制資料安全生成圖片物件
UIImage *image = [UIImage af_safeImageWithData:data];
// 如果是gif圖就直接返回圖片物件
if (image.images) {
return image;
}
// 生成對應縮放因子的圖片
return [[UIImage alloc] initWithCGImage:[image CGImage] scale:scale orientation:image.imageOrientation];
}
/**
生成對應縮放因子的圖片並進行解壓
*/
static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale) {
// 如果沒有資料就直接返回
if (!data || [data length] == 0) {
return nil;
}
// 建立畫布和圖片資料提供者
CGImageRef imageRef = NULL;
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
// 如果是png格式直接解壓
if ([response.MIMEType isEqualToString:@"image/png"]) {
imageRef = CGImageCreateWithPNGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
// 如果是jpg格式
} else if ([response.MIMEType isEqualToString:@"image/jpeg"]) {
// 先進行解壓
imageRef = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
// 如果解壓成功
if (imageRef) {
CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(imageRef);
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(imageColorSpace);
// 如果jpg的色彩空間是CMKY而不是RGB的話,不進行解壓
// CGImageCreateWithJPEGDataProvider does not properly handle CMKY, so fall back to AFImageWithDataAtScale
if (imageColorSpaceModel == kCGColorSpaceModelCMYK) {
CGImageRelease(imageRef);
imageRef = NULL;
}
}
}
// 釋放圖片資料提供者
CGDataProviderRelease(dataProvider);
// 按照縮放因子生成對應的圖片
UIImage *image = AFImageWithDataAtScale(data, scale);
// 如果沒有解壓成功
if (!imageRef) {
// 如果是gif圖或者沒有生成圖片就直接返回
if (image.images || !image) {
return image;
}
// 如果沒有生成圖片畫布就直接返回
imageRef = CGImageCreateCopy([image CGImage]);
if (!imageRef) {
return nil;
}
}
// 獲取圖片尺寸和一個畫素佔用的位元組數
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
// 如果圖片太大或者一個畫素佔用的位元組超過8就不解壓了
if (width * height > 1024 * 1024 || bitsPerComponent > 8) {
CGImageRelease(imageRef);
return image;
}
// 配置繪圖上下文的引數
// CGImageGetBytesPerRow() calculates incorrectly in iOS 5.0, so defer to CGBitmapContextCreate
size_t bytesPerRow = 0;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
if (colorSpaceModel == kCGColorSpaceModelRGB) {
uint32_t alpha = (bitmapInfo & kCGBitmapAlphaInfoMask);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wassign-enum"
if (alpha == kCGImageAlphaNone) {
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaNoneSkipFirst;
} else if (!(alpha == kCGImageAlphaNoneSkipFirst || alpha == kCGImageAlphaNoneSkipLast)) {
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaPremultipliedFirst;
}
#pragma clang diagnostic pop
}
// 生成繪圖上下文
CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
// 釋放色彩空間
CGColorSpaceRelease(colorSpace);
// 如果沒有成功生成繪圖上下文就釋放畫布並直接返回
if (!context) {
CGImageRelease(imageRef);
return image;
}
// 繪製圖片
CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, width, height), imageRef);
CGImageRef inflatedImageRef = CGBitmapContextCreateImage(context);
// 釋放繪圖上下文
CGContextRelease(context);
// 生成圖片
UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation];
// 釋放畫布
CGImageRelease(inflatedImageRef);
CGImageRelease(imageRef);
return inflatedImage;
}
複製程式碼
為什麼要費這麼大勁解壓圖片呢?當我們呼叫UIImage
的方法imageWithData:
方法把資料轉成UIImage
物件後,其實這時UIImage
物件還沒準備好需要渲染到螢幕的資料,現在的網路影象PNG和JPG都是壓縮格式,需要把它們解壓轉成bitmap後才能渲染到螢幕上,如果不做任何處理,當你把UIImage
賦給UIImageView
,在渲染之前底層會判斷到UIImage
物件未解壓,沒有bitmap資料,這時會在主執行緒對圖片進行解壓操作,再渲染到螢幕上。這個解壓操作是比較耗時的,如果任由它在主執行緒做,可能會導致速度慢UI卡頓的問題。
AFImageResponseSerializer
除了把返回資料解析成UIImage
外,還會把影象資料解壓,這個處理是在子執行緒(AFNetworking
專用的一條執行緒,詳見AFURLConnectionOperation
),處理後上層使用返回的UIImage
在主執行緒渲染時就不需要做解壓這步操作,主執行緒減輕了負擔,減少了UI卡頓問題。
具體實現上在AFInflatedImageFromResponseWithDataAtScale
裡,建立一個畫布,把UIImage
畫在畫布上,再把這個畫布儲存成UIImage
返回給上層。只有JPG和PNG才會嘗試去做解壓操作,期間如果解壓失敗,或者遇到CMKY顏色格式的jpg,或者影象太大(解壓後的bitmap太佔記憶體,一個畫素3-4位元組,搞不好記憶體就爆掉了),就直接返回未解壓的影象。
另外在程式碼裡看到iOS才需要這樣手動解壓,MacOS上已經有封裝好的物件NSBitmapImageRep可以做這個事。以上解釋摘自這篇部落格
5.一個私有分類
5.1.類擴充套件
/**
利用二級制資料安全生成圖片物件的方法
*/
+ (UIImage *)af_safeImageWithData:(NSData *)data;
複製程式碼
5.2.靜態物件
/**
宣告瞭一個鎖物件用於安全生成圖片
*/
static NSLock* imageLock = nil;
複製程式碼
5.3.私有實現
+ (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;
}
複製程式碼
原始碼閱讀系列:AFNetworking
原始碼閱讀:AFNetworking(二)——AFURLRequestSerialization
原始碼閱讀:AFNetworking(三)——AFURLResponseSerialization
原始碼閱讀:AFNetworking(四)——AFSecurityPolicy
原始碼閱讀:AFNetworking(五)——AFNetworkReachabilityManager
原始碼閱讀:AFNetworking(六)——AFURLSessionManager
原始碼閱讀:AFNetworking(七)——AFHTTPSessionManager
原始碼閱讀:AFNetworking(八)——AFAutoPurgingImageCache
原始碼閱讀:AFNetworking(九)——AFImageDownloader
原始碼閱讀:AFNetworking(十)——AFNetworkActivityIndicatorManager
原始碼閱讀:AFNetworking(十一)——UIActivityIndicatorView+AFNetworking
原始碼閱讀:AFNetworking(十二)——UIButton+AFNetworking
原始碼閱讀:AFNetworking(十三)——UIImageView+AFNetworking
原始碼閱讀:AFNetworking(十四)——UIProgressView+AFNetworking