技术干货程序员我爱编程

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

2018-05-25  本文已影响119人  kirito_song

目录

前言

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

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

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

核心代码

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

首先、我们可以先看一下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。我们完全可以自定义出一种解析方式、只要在协议中执行即可。

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

所有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能否被响应

我们注意到、上面的两个判断都会产生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;
}

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

针对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

同样在其他子类里也会用到、不再一一指出

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

也是解析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的协议实现

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

图像格式化

/**
 图像格式化

 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将会自动解压一次。
在这里提前解压了、也就不会再主线程做出解压的操作。

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

@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 文件的优化

上一篇下一篇

猜你喜欢

热点阅读