美文网首页技术干货程序员我爱编程
iOS源码补完计划--AFNetworking(五)

iOS源码补完计划--AFNetworking(五)

作者: kirito_song | 来源:发表于2018-05-25 13:50 被阅读119次

    目录

    • 前言
    • 核心代码
      • AFURLResponseSerialization协议
      • AFHTTPResponseSerializer
        • AFJSONResponseSerializer
        • AFXMLParserResponseSerializer
        • AFXMLDocumentResponseSerializer
        • AFPropertyListResponseSerializer
        • AFImageResponseSerializer
        • AFCompoundResponseSerializer
    • APIDemo
    • 参考资料

    前言

    AFNetworking源码第五篇、大概也是最后一篇
    主要看了看AFURLResponseSerialization的内容
    负责网络请求成功之后服务器返回的响应体进行格式化

    代码乍看起来也挺多、但实际上大部分都是作为AFURLResponseSerialization子类、将不同格式(JSON/XML/PList等格式)分别处理的重复逻辑。读起来相对轻松很多。
    但其中有很多小知识点。

    AFN概述:《iOS源码补完计划--AFNetworking 3.1.0源码研读》

    核心代码

    • AFURLResponseSerialization协议

    协议中包含一个解码方法、所有的请求在结束时、都需要通过这个协议方法进行解码

    首先、我们可以先看一下AFHTTPSessionManager以及AFURLSessionManager中的解析器属性。

    /**
        解码器、负责响应解码。比如json格式等等
     */
    @property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
    
    /**
     网络请求返回的数据对象
     */
    @property (nonatomic, strong) id <AFURLResponseSerialization> responseSerializer;
    

    他们都遵循着一个协议AFURLResponseSerialization
    就说明这个协议、是解码的关键。
    点击进去:

    /**
        AFURLResponseSerialization协议、同时需要遵循NSSecureCoding, NSCopying两个协议
     */
    @protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>
    
    
    /**
     
     必须实现的方法
     将响应体进行指定方式解码
    
     @param response 需要处理的`NSURLResponse`
     @param data 需要处理的数据
     @param error 错误
    
     @return 返回一个特定格式(Dic/Arr/Str/XML/Plist/图片等)
     
     比如AFURLSessionManager中任务结束后对data的转码时就这样使用:
     responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
     
     其中manager.responseSerializer为`AFJSONRequestSerializer`或者`AFPropertyListRequestSerializer`、均为`AFHTTPRequestSerializer(遵循AFURLResponseSerialization协议)`的子类。
     */
    - (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、也就是请求结束的方法中被调用了:

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

    也就是说、只要你主动实现了这个代理方法、并且将它关联给AFURLSessionManager或者AFHTTPSessionManager。我们完全可以自定义出一种解析方式、只要在协议中执行即可。

    这是协议代理一种很经典的用法。也是解耦的时候代替继承、然后重载父类方法时通用做法。
    在多人协作的时候、约定好协议然后交由其他业务实现、也是提升开发效率很普遍的方式。

    • AFHTTPResponseSerializer

    所有AFResponseSerializer解析器(AFJSONResponseSerializer/AFXMLDocumentResponseSerializer等等)的父类。对HTTP属性(HTTPCodeContent-Type)进行特化。

    @interface AFHTTPResponseSerializer : NSObject <AFURLResponseSerialization>
    
    //初始化
    - (instancetype)init;
    
    /**
        解码器的编码方式
     */
    @property (nonatomic, assign) NSStringEncoding stringEncoding;
    
    /**
        初始化
     */
    + (instancetype)serializer;
    
    ///-----------------------------------------
    /// @name 配置响应解码器
    ///-----------------------------------------
    
    /**
     接受并解析的HTTP状态码。如果不为nil、未包含的状态码将不被解析
    
     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;
    
    /**
     检测响应能否被解析
     
     @param response 响应
     @param data 二进制文件
     @param error 错误
     @return 能否被解析
     */
    - (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response
                        data:(nullable NSData *)data
                       error:(NSError * _Nullable __autoreleasing *)error;
    
    @end
    

    提供了编码方式、状态码集合、Content-Type集合、以及判断能否被解析的方法一个。

    @implementation AFHTTPResponseSerializer
    
    + (instancetype)serializer {
        return [[self alloc] init];
    }
    
    - (instancetype)init {
        self = [super init];
        if (!self) {
            return nil;
        }
    
        self.stringEncoding = NSUTF8StringEncoding;
        
        /*
            NSIndexSet是一个无符号整数的集合、内部元素具有唯一性
            将200 - 299的状态码全部添加
            等同于[indexSetM addIndexesInRange:NSMakeRange(200, 100)];
         */
        self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
        self.acceptableContentTypes = nil;
    
        return self;
    }
    

    初始化、没什么好说的。需要注意的是NSIndexSet这个合集、是NSSet的数字版。使用的话注释里已经写了。

    /**
     检测响应能否被解析
    
     @param response 响应
     @param data 二进制文件
     @param error 错误
     @return 能否被解析
     */
    - (BOOL)validateResponse:(NSHTTPURLResponse *)response
                        data:(NSData *)data
                       error:(NSError * __autoreleasing *)error
    {
        BOOL responseIsValid = YES;
        //验证的错误根据
        NSError *validationError = nil;
    
        //如果response 为 NSHTTPURLResponse实例
        if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
            //设置了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;
            }
    
            //设置了acceptableStatusCodes && statusCode(状态码)不允许接受 && 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能否被响应

    • 如果是HTTPRequest的话会根据MIMEType(Content-Type)以及statusCode(状态码)结合之前的两个属性集合进行判断。
    • 如果不是HTTPRequest、直接跳过返回YES。
      这也能很好的解释。为什么没有AFURLResponseSerializer、而直接就是HTTPResponseSerializer

    我们注意到、上面的两个判断都会产生NSError。
    那么这两个NSError如何被一个**error捕获并且传递呢?

    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;
    
        //用主错误的domain、code、(包含优先错误的)新错误信息生成一个新错误。
        return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
    }
    

    其中用到了NSUnderlyingErrorKey这个系统的key、代表优先错误。
    如此、可以很容易的在NSError中嵌套另一个NSError。

    - (id)responseObjectForResponse:(NSURLResponse *)response
                               data:(NSData *)data
                              error:(NSError *__autoreleasing *)error
    {
        
        [self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
        //AFHTTPResponseSerializer 中这个数据转换的协议方法并不起什么作用、直接返回了data
        return data;
    }
    

    这个方法看着没什么意义、不过确实也是...唯一的作用是为了当子类没有实现这个协议方法的时候、程序不会崩溃。

    • AFJSONResponseSerializer

    针对JSON格式特化的解析器、继承AFHTTPResponseSerializer
    可以指定返回的对象(字典、数组、字符串、是否可变)。
    还可以排空

    /**
     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;
    
    /**
     根据指定策略创建一个实例
     */
    + (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指定了服务器返回的content-type必须是JSON。

    其他的子类也做了相同的操作。就不在一一指出了。

    - (id)responseObjectForResponse:(NSURLResponse *)response
                               data:(NSData *)data
                              error:(NSError *__autoreleasing *)error
    {
        //检测这个响应是否可以被解析
        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;
        }
    
        //删除响应里的NSNULL(内部可以递归)
        if (self.removesKeysWithNullValues && responseObject) {
            responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
        }
    
        if (error) {
            //整合error
            *error = AFErrorWithUnderlyingError(serializationError, *error);
        }
    
        return responseObject;
    }
    

    实现的代理方法、先确定是否可以解析、然后进行排空操作
    除了注释之外、需要注意的是还有两个函数。

    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]) {
            //判断优先错误中domain和code是否符合
            return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
        }
    
        return NO;
    }
    

    判断NSError符不符合对应code以及domain(也就是之前对contenttype或者httpcode判断出错时加进去的code以及domain)。用到了递归

    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) {
                //将数组里不为NULL的元素转译进来
                [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
            }
    
            //如果是NSJSONReadingMutableContainers、则返回一个可变的数组。否则返回一个不可变的
            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是数组或者字典、递归排空
                    mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
                }
            }
            //如果是NSJSONReadingMutableContainers、则返回一个可变的字典。否则返回一个不可变的
            return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
        }
    
        //不是数组也不是字典、返回原对象(应该就是个字符串了)
        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解析的协议实现

    • 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的协议实现

    • 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) {
            //转化成NSPropertyListSerialization对象
            responseObject = [NSPropertyListSerialization propertyListWithData:data options:self.readOptions format:NULL error:&serializationError];
        }
    
        if (error) {
            *error = AFErrorWithUnderlyingError(serializationError, *error);
        }
    
        return responseObject;
    }
    

    PList解析的协议实现

    • 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
    

    解析图片的子类.可以设置图片比例、以及是否在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;
    }
    

    这里我们发现有两个函数

    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);
    
        //判断响应返回的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;
    }
    

    这个函数具体的写法设计很多CG层面的代码、并不了解。
    但作用查到了:

    这里返回的UIImage是含有bitmap数据的(因为通过解压出的bitmap绘制出的CGImage)
    bitmap的作用在于在将UIImage交付给UIImageView的时候。如果没有bitmap将会自动解压一次。
    在这里提前解压了、也就不会再主线程做出解压的操作。

    • AFCompoundResponseSerializer

    可以包含多个解析器的复合解析器

    @interface AFCompoundResponseSerializer : AFHTTPResponseSerializer
    
    /**
     解析器数组
     */
    @property (readonly, nonatomic, copy) NSArray <id<AFURLResponseSerialization>> *responseSerializers;
    
    /**
     初始化
     */
    + (instancetype)compoundSerializerWithResponseSerializers:(NSArray <id<AFURLResponseSerialization>> *)responseSerializers;
    
    @end
    

    你可以使用多个实现了AFURLResponseSerialization(解码)协议的解析器来初始化它。

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

    内部会遍历所有的解析器、哪个能解析当前响应体、就用哪个解析。


    APIDemo

    说实话两个文件加起来两千多行、每行都想顾及到我自己都不知道怎么写...但是把很多代码都进行了注释、有兴趣可以自取:

    GitHub


    参考资料

    AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization
    Plist 文件的优化

    相关文章

      网友评论

      本文标题:iOS源码补完计划--AFNetworking(五)

      本文链接:https://www.haomeiwen.com/subject/eajyjftx.html