iOS源碼補完計劃--AFNetworking(五)

目錄

  • 前言
  • 核心代碼
    • AFURLResponseSerialization協(xié)議
    • AFHTTPResponseSerializer
      • AFJSONResponseSerializer
      • AFXMLParserResponseSerializer
      • AFXMLDocumentResponseSerializer
      • AFPropertyListResponseSerializer
      • AFImageResponseSerializer
      • AFCompoundResponseSerializer
  • APIDemo
  • 參考資料

前言

AFNetworking源碼第五篇、大概也是最后一篇
主要看了看AFURLResponseSerialization的內(nèi)容
負責(zé)網(wǎng)絡(luò)請求成功之后服務(wù)器返回的響應(yīng)體進行格式化

代碼乍看起來也挺多洽洁、但實際上大部分都是作為AFURLResponseSerialization子類毛仪、將不同格式(JSON/XML/PList等格式)分別處理的重復(fù)邏輯偎肃。讀起來相對輕松很多。
但其中有很多小知識點。

AFN概述:《iOS源碼補完計劃--AFNetworking 3.1.0源碼研讀》

核心代碼

  • AFURLResponseSerialization協(xié)議

協(xié)議中包含一個解碼方法、所有的請求在結(jié)束時骇吭、都需要通過這個協(xié)議方法進行解碼

首先、我們可以先看一下AFHTTPSessionManager以及AFURLSessionManager中的解析器屬性歧寺。

/**
    解碼器燥狰、負責(zé)響應(yīng)解碼。比如json格式等等
 */
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;

/**
 網(wǎng)絡(luò)請求返回的數(shù)據(jù)對象
 */
@property (nonatomic, strong) id <AFURLResponseSerialization> responseSerializer;

他們都遵循著一個協(xié)議AFURLResponseSerialization斜筐、
就說明這個協(xié)議龙致、是解碼的關(guān)鍵。
點擊進去:

/**
    AFURLResponseSerialization協(xié)議奴艾、同時需要遵循NSSecureCoding, NSCopying兩個協(xié)議
 */
@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>


/**
 
 必須實現(xiàn)的方法
 將響應(yīng)體進行指定方式解碼

 @param response 需要處理的`NSURLResponse`
 @param data 需要處理的數(shù)據(jù)
 @param error 錯誤

 @return 返回一個特定格式(Dic/Arr/Str/XML/Plist/圖片等)
 
 比如AFURLSessionManager中任務(wù)結(jié)束后對data的轉(zhuǎn)碼時就這樣使用:
 responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
 
 其中manager.responseSerializer為`AFJSONRequestSerializer`或者`AFPropertyListRequestSerializer`净当、均為`AFHTTPRequestSerializer(遵循AFURLResponseSerialization協(xié)議)`的子類。
 */
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

@end

搜索這個方法蕴潦、果不其然像啼。
只在AFURLSessionManager
==>AFURLSessionManagerTaskDelegate
==>- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error、也就是請求結(jié)束的方法中被調(diào)用了:

__block id responseObject = nil;
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];

也就是說潭苞、只要你主動實現(xiàn)了這個代理方法忽冻、并且將它關(guān)聯(lián)給AFURLSessionManager或者AFHTTPSessionManager。我們完全可以自定義出一種解析方式此疹、只要在協(xié)議中執(zhí)行即可僧诚。

這是協(xié)議代理一種很經(jīng)典的用法。也是解耦的時候代替繼承蝗碎、然后重載父類方法時通用做法湖笨。
在多人協(xié)作的時候、約定好協(xié)議然后交由其他業(yè)務(wù)實現(xiàn)蹦骑、也是提升開發(fā)效率很普遍的方式慈省。

  • AFHTTPResponseSerializer

所有AFResponseSerializer解析器(AFJSONResponseSerializer/AFXMLDocumentResponseSerializer等等)的父類。對HTTP屬性(HTTPCode眠菇、Content-Type)進行特化边败。

@interface AFHTTPResponseSerializer : NSObject <AFURLResponseSerialization>

//初始化
- (instancetype)init;

/**
    解碼器的編碼方式
 */
@property (nonatomic, assign) NSStringEncoding stringEncoding;

/**
    初始化
 */
+ (instancetype)serializer;

///-----------------------------------------
/// @name 配置響應(yīng)解碼器
///-----------------------------------------

/**
 接受并解析的HTTP狀態(tài)碼。如果不為nil捎废、未包含的狀態(tài)碼將不被解析

 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
 */
@property (nonatomic, copy, nullable) NSIndexSet *acceptableStatusCodes;

/**
 接受并解析的Content-Type笑窜。如果不為nil、未包含的Content-Type將不被解析
 */
@property (nonatomic, copy, nullable) NSSet <NSString *> *acceptableContentTypes;

/**
 檢測響應(yīng)能否被解析
 
 @param response 響應(yīng)
 @param data 二進制文件
 @param error 錯誤
 @return 能否被解析
 */
- (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response
                    data:(nullable NSData *)data
                   error:(NSError * _Nullable __autoreleasing *)error;

@end

提供了編碼方式登疗、狀態(tài)碼集合排截、Content-Type集合、以及判斷能否被解析的方法一個。

@implementation AFHTTPResponseSerializer

+ (instancetype)serializer {
    return [[self alloc] init];
}

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }

    self.stringEncoding = NSUTF8StringEncoding;
    
    /*
        NSIndexSet是一個無符號整數(shù)的集合匾寝、內(nèi)部元素具有唯一性
        將200 - 299的狀態(tài)碼全部添加
        等同于[indexSetM addIndexesInRange:NSMakeRange(200, 100)];
     */
    self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
    self.acceptableContentTypes = nil;

    return self;
}

初始化搬葬、沒什么好說的荷腊。需要注意的是NSIndexSet這個合集艳悔、是NSSet的數(shù)字版。使用的話注釋里已經(jīng)寫了女仰。

/**
 檢測響應(yīng)能否被解析

 @param response 響應(yīng)
 @param data 二進制文件
 @param error 錯誤
 @return 能否被解析
 */
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
                    data:(NSData *)data
                   error:(NSError * __autoreleasing *)error
{
    BOOL responseIsValid = YES;
    //驗證的錯誤根據(jù)
    NSError *validationError = nil;

    //如果response 為 NSHTTPURLResponse實例
    if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
        //設(shè)置了acceptableContentTypes && MIMEType(Content-Type)不允許接受 && MIMEType以及data 存在
        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`對象是不存在主錯誤的。
                validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
            }

            responseIsValid = NO;
        }

        //設(shè)置了acceptableStatusCodes && statusCode(狀態(tài)碼)不允許接受 && 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`對象是不存在主錯誤的乔外。
            //如果兩個判斷都出錯了、整合出來的`validationError`對象的主錯誤(error.userInfo[NSUnderlyingErrorKey])為之前content-type的error一罩。本次error信息包含在userInfo里杨幼。
            validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);

            responseIsValid = NO;
        }
    }

    //如果不被接受、將error傳遞
    if (error && !responseIsValid) {
        *error = validationError;
    }

    //返回是否有效
    return responseIsValid;
}

這個方法可以檢測當前返回的Request能否被響應(yīng)

  • 如果是HTTPRequest的話會根據(jù)MIMEType(Content-Type)以及statusCode(狀態(tài)碼)結(jié)合之前的兩個屬性集合進行判斷聂渊。
  • 如果不是HTTPRequest差购、直接跳過返回YES。
    這也能很好的解釋汉嗽。為什么沒有AFURLResponseSerializer欲逃、而直接就是HTTPResponseSerializer

我們注意到、上面的兩個判斷都會產(chǎn)生NSError饼暑。
那么這兩個NSError如何被一個**error捕獲并且傳遞呢稳析?

static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
    //如果主錯誤為空、返回優(yōu)先錯誤
    if (!error) {
        //返回優(yōu)先錯誤
        return underlyingError;
    }

    //如果沒有優(yōu)先錯誤 或者 主錯誤里存在優(yōu)先錯誤
    if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
        //直接返回主錯誤
        return error;
    }

    //重新生成錯誤信息
    NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
    //并且將優(yōu)先錯誤弓叛、添加到錯誤信息的優(yōu)先錯誤中
    mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;

    //用主錯誤的domain彰居、code、(包含優(yōu)先錯誤的)新錯誤信息生成一個新錯誤撰筷。
    return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}

其中用到了NSUnderlyingErrorKey這個系統(tǒng)的key陈惰、代表優(yōu)先錯誤。
如此闭专、可以很容易的在NSError中嵌套另一個NSError奴潘。

- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    
    [self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
    //AFHTTPResponseSerializer 中這個數(shù)據(jù)轉(zhuǎn)換的協(xié)議方法并不起什么作用、直接返回了data
    return data;
}

這個方法看著沒什么意義影钉、不過確實也是...唯一的作用是為了當子類沒有實現(xiàn)這個協(xié)議方法的時候画髓、程序不會崩潰。

  • AFJSONResponseSerializer

針對JSON格式特化的解析器平委、繼承AFHTTPResponseSerializer奈虾。
可以指定返回的對象(字典、數(shù)組、字符串肉微、是否可變)匾鸥。
還可以排空

/**
 Json序列化

 默認接收一下幾種類型的content-type

 - `application/json`
 - `text/json`
 - `text/javascript`
 */
@interface AFJSONResponseSerializer : AFHTTPResponseSerializer
//初始化
- (instancetype)init;

/**
    讀取JSON文件的選項 默認 0
 
     typedef NS_OPTIONS(NSUInteger, NSJSONReadingOptions) {
     NSJSONReadingMutableContainers = (1UL << 0),//返回一個MDic/MArr
     NSJSONReadingMutableLeaves = (1UL << 1),//返回一個MStr
     NSJSONReadingAllowFragments = (1UL << 2)//允許解析最外層不是Dic或者Arr的Json、比如@"123"
     } API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
 
 */
@property (nonatomic, assign) NSJSONReadingOptions readingOptions;

/**
 是否屏蔽NSNULL碉纳、默認為NO
 */
@property (nonatomic, assign) BOOL removesKeysWithNullValues;

/**
 根據(jù)指定策略創(chuàng)建一個實例
 */
+ (instancetype)serializerWithReadingOptions:(NSJSONReadingOptions)readingOptions;

@end

為JSON格式的解析特化了一些屬性

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }
    //默認可解析的content-type為'application/json", @"text/json", @"text/javascript'
    self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];

    return self;
}

這里勿负、利用父類(AFHTTPResponseSerializer)屬性acceptableContentTypes指定了服務(wù)器返回的content-type必須是JSON。

其他的子類也做了相同的操作劳曹。就不在一一指出了奴愉。

- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    //檢測這個響應(yīng)是否可以被解析
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
        //不能的話。
        //如果error為空||
        //error.code(或者error.userInfo[NSUnderlyingErrorKey]的主錯誤)=NSURLErrorCannotDecodeContentData
        //(domain = AFURLResponseSerializationErrorDomain)的情況下是不能被解析的铁孵。
        
        //總之就是上面那個是否可以被解析的判斷出錯了锭硼、并且按照正常流程搞出了error(也就是確定是contenttype或者httpcode不匹配)
        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;
    }

    //刪除響應(yīng)里的NSNULL(內(nèi)部可以遞歸)
    if (self.removesKeysWithNullValues && responseObject) {
        responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
    }

    if (error) {
        //整合error
        *error = AFErrorWithUnderlyingError(serializationError, *error);
    }

    return responseObject;
}

實現(xiàn)的代理方法、先確定是否可以解析蜕劝、然后進行排空操作
除了注釋之外檀头、需要注意的是還有兩個函數(shù)。

static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
    //判斷error中的domain和code是否符合
    if ([error.domain isEqualToString:domain] && error.code == code) {
        return YES;
    } else if (error.userInfo[NSUnderlyingErrorKey]) {
        //判斷優(yōu)先錯誤中domain和code是否符合
        return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
    }

    return NO;
}

判斷NSError符不符合對應(yīng)code以及domain(也就是之前對contenttype或者httpcode判斷出錯時加進去的code以及domain)岖沛。用到了遞歸

static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
    //數(shù)組
    if ([JSONObject isKindOfClass:[NSArray class]]) {
        //生成一個可變數(shù)組
        NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
        for (id value in (NSArray *)JSONObject) {
            //將數(shù)組里不為NULL的元素轉(zhuǎn)譯進來
            [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
        }

        //如果是NSJSONReadingMutableContainers暑始、則返回一個可變的數(shù)組。否則返回一個不可變的
        return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
    } else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
        //字典
        NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
        //遍歷所有key
        for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
            
            id value = (NSDictionary *)JSONObject[key];
            if (!value || [value isEqual:[NSNull null]]) {
                //value為空則移除
                [mutableDictionary removeObjectForKey:key];
            } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
                //如果value是數(shù)組或者字典烫止、遞歸排空
                mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
            }
        }
        //如果是NSJSONReadingMutableContainers蒋荚、則返回一個可變的字典。否則返回一個不可變的
        return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
    }

    //不是數(shù)組也不是字典馆蠕、返回原對象(應(yīng)該就是個字符串了)
    return JSONObject;
}

刪除對象中的NSNULL

同樣在其他子類里也會用到期升、不再一一指出

  • AFXMLParserResponseSerializer

XML解析器

@interface AFXMLParserResponseSerializer : AFHTTPResponseSerializer

@end

頭文件里并沒有什么新增的屬性

//XML解析
- (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;
        }
    }

    //返回XML對象
    return [[NSXMLParser alloc] initWithData:data];
}

XML解析的協(xié)議實現(xiàn)

  • AFXMLDocumentResponseSerializer

也是解析XML
這個子類只在mac os x上使用

@interface AFXMLDocumentResponseSerializer : AFHTTPResponseSerializer

- (instancetype)init;

/**
 Input and output options specifically intended for `NSXMLDocument` objects. For possible values, see the `NSJSONSerialization` documentation section "NSJSONReadingOptions". `0` by default.
 */
@property (nonatomic, assign) NSUInteger options;

/**
 Creates and returns an XML document serializer with the specified options.

 @param mask The XML document options.
 */
+ (instancetype)serializerWithXMLDocumentOptions:(NSUInteger)mask;

@end
- (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;
        }
    }

    NSError *serializationError = nil;
    NSXMLDocument *document = [[NSXMLDocument alloc] initWithData:data options:self.options error:&serializationError];

    if (error) {
        *error = AFErrorWithUnderlyingError(serializationError, *error);
    }

    return document;
}

MAC上解析XML的協(xié)議實現(xiàn)

  • AFPropertyListResponseSerializer

PList解析器

/**
 PList序列化

 支持以下MIME types:
 - `application/x-plist`
 */
@interface AFPropertyListResponseSerializer : AFHTTPResponseSerializer

- (instancetype)init;

/**
 PList 格式
 typedef NS_ENUM(NSUInteger, NSPropertyListFormat) {
 NSPropertyListOpenStepFormat = kCFPropertyListOpenStepFormat,
 //指定屬性列表文件格式為XML格式,仍然是純文本類型互躬,不會壓縮文件
 NSPropertyListXMLFormat_v1_0 = kCFPropertyListXMLFormat_v1_0,
 //指定屬性列表文件格式為二進制格式播赁,文件是二進制類型,會壓縮文件
 NSPropertyListBinaryFormat_v1_0 = kCFPropertyListBinaryFormat_v1_0
 //指定屬性列表文件格式為ASCII碼格式吼渡,對于舊格式的屬性列表文件容为,不支持寫入操作 
 };
 */
@property (nonatomic, assign) NSPropertyListFormat format;

/**
 PList 讀取選項
 };
 */
@property (nonatomic, assign) NSPropertyListReadOptions readOptions;

/**
 Creates and returns a property list serializer with a specified format, read options, and write options.

 @param format The property list format.
 @param readOptions The property list reading options.
 */
+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
                         readOptions:(NSPropertyListReadOptions)readOptions;

@end

將JSON解析成PList

- (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;
    NSError *serializationError = nil;

    if (data) {
        //轉(zhuǎn)化成NSPropertyListSerialization對象
        responseObject = [NSPropertyListSerialization propertyListWithData:data options:self.readOptions format:NULL error:&serializationError];
    }

    if (error) {
        *error = AFErrorWithUnderlyingError(serializationError, *error);
    }

    return responseObject;
}

PList解析的協(xié)議實現(xiàn)

  • AFImageResponseSerializer

圖像格式化

/**
 圖像格式化

 MIME types:
 - `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

#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
/**
 圖片比例
 */
@property (nonatomic, assign) CGFloat imageScale;

/**
 是否自動壓縮圖片(如PNG/JPEG)
 當使用`setCompletionBlockWithSuccess:failure:`時、這個選項可以顯著的提高性能
 默認YES
 */
@property (nonatomic, assign) BOOL automaticallyInflatesResponseImage;
#endif

@end

解析圖片的子類.可以設(shè)置圖片比例寺酪、以及是否在AFN線程解壓圖片

- (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);
    }
#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;
}

這里我們發(fā)現(xiàn)有兩個函數(shù)

static UIImage * AFImageWithDataAtScale(NSData *data, CGFloat scale) {
    UIImage *image = [UIImage af_safeImageWithData:data];
    if (image.images) {
        return image;
    }
    
    return [[UIImage alloc] initWithCGImage:[image CGImage] scale:scale orientation:image.imageOrientation];
}

直接用NSData生成UIImage

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);

    //判斷響應(yīng)返回的MIMEType類型坎背,
    if ([response.MIMEType isEqualToString:@"image/png"]) {
        //PNG
        imageRef = CGImageCreateWithPNGDataProvider(dataProvider,  NULL, true, kCGRenderingIntentDefault);
    } else if ([response.MIMEType isEqualToString:@"image/jpeg"]) {
        //JPEG
        imageRef = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);

        if (imageRef) {
            CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(imageRef);
            CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(imageColorSpace);

            // 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) {
        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);

    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;
}

這個函數(shù)具體的寫法設(shè)計很多CG層面的代碼、并不了解寄雀。
但作用查到了:

這里返回的UIImage是含有bitmap數(shù)據(jù)的(因為通過解壓出的bitmap繪制出的CGImage)
bitmap的作用在于在將UIImage交付給UIImageView的時候得滤。如果沒有bitmap將會自動解壓一次。
在這里提前解壓了盒犹、也就不會再主線程做出解壓的操作懂更。

  • AFCompoundResponseSerializer

可以包含多個解析器的復(fù)合解析器

@interface AFCompoundResponseSerializer : AFHTTPResponseSerializer

/**
 解析器數(shù)組
 */
@property (readonly, nonatomic, copy) NSArray <id<AFURLResponseSerialization>> *responseSerializers;

/**
 初始化
 */
+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray <id<AFURLResponseSerialization>> *)responseSerializers;

@end

你可以使用多個實現(xiàn)了AFURLResponseSerialization(解碼)協(xié)議的解析器來初始化它眨业。

- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    //遍歷所有解析器、那個能解析就用哪個
    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;
        }
    }

    return [super responseObjectForResponse:response data:data error:error];
}

內(nèi)部會遍歷所有的解析器沮协、哪個能解析當前響應(yīng)體龄捡、就用哪個解析。


APIDemo

說實話兩個文件加起來兩千多行慷暂、每行都想顧及到我自己都不知道怎么寫...但是把很多代碼都進行了注釋聘殖、有興趣可以自取:

GitHub


最后

本文主要是自己的學(xué)習(xí)與總結(jié)。如果文內(nèi)存在紕漏呜呐、萬望留言斧正就斤。如果不吝賜教小弟更加感謝悍募。


參考資料

AFNetworking 3.0 源碼解讀(四)之 AFURLResponseSerialization
Plist 文件的優(yōu)化

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蘑辑,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子坠宴,更是在濱河造成了極大的恐慌洋魂,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喜鼓,死亡現(xiàn)場離奇詭異副砍,居然都是意外死亡,警方通過查閱死者的電腦和手機庄岖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門豁翎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人隅忿,你說我怎么就攤上這事心剥。” “怎么了背桐?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵优烧,是天一觀的道長。 經(jīng)常有香客問我链峭,道長畦娄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任弊仪,我火速辦了婚禮熙卡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘励饵。我一直安慰自己驳癌,他們只是感情好,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布曲横。 她就那樣靜靜地躺著喂柒,像睡著了一般不瓶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上灾杰,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天蚊丐,我揣著相機與錄音,去河邊找鬼艳吠。 笑死麦备,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的昭娩。 我是一名探鬼主播凛篙,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼栏渺!你這毒婦竟也來了呛梆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤磕诊,失蹤者是張志新(化名)和其女友劉穎填物,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體霎终,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡滞磺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了莱褒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片击困。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖广凸,靈堂內(nèi)的尸體忽然破棺而出阅茶,到底是詐尸還是另有隱情,我是刑警寧澤炮障,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布目派,位于F島的核電站,受9級特大地震影響胁赢,放射性物質(zhì)發(fā)生泄漏企蹭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一智末、第九天 我趴在偏房一處隱蔽的房頂上張望谅摄。 院中可真熱鬧,春花似錦系馆、人聲如沸送漠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闽寡。三九已至代兵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間爷狈,已是汗流浹背植影。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留涎永,地道東北人思币。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像羡微,于是被迫代替她去往敵國和親谷饿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354