AFN 3.0学习总结(四)

参考:AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization

说明:很多内容都是摘抄原文,只是根据自己的需要进行摘抄或者总结,如有不妥请及时指出,谢谢。

这次主要讲AFURLResponseSerialization(HTTP响应)这一个类的知识。

一、AFURLResponseSerialization

AFN 3.0学习总结(四)_第1张图片
1

这是一个协议,只要遵循这个协议,就必须实现NSSecureCoding、NSCoping这两个协议,同时还要实现上图中的第二个方法。

第二个方法返回序列化后的结果。不管是AFHttpResponseSerialize,还是他的子类,都遵循这个协议,也就是在各自的实现中实现了这个协议,然后返回了属于自己的结果。

ps:根据这个协议,我有了一些启发。当我们在设计一个网络框架的时候,因为业务不同,返回的数据也有很多种,通常的一种做法是直接返回服务器响应的数据,由业务人员自己实现业务。但是如果业务繁杂,这样写出的代码也会很乱,我们不妨采用类似这种协议的设计模式,这样做有两个好处:

1. 业务人员和数据人员可以分开。 数据提前约定好名称和内容,写数据人员实现数据部分,写业务人员实现业务部分。

2. 左右的数据转换放到协议实现方法中,出现问题,更容易查找问题。

由于这个类有很多的子类,我们先来看看这些类的组成,然后逐一对每个子类的代码进行解读

AFN 3.0学习总结(四)_第2张图片

=============================  分割线==============================

我们还是先来看看AFHTTPResponseSerializer的头文件组成部分

AFN 3.0学习总结(四)_第3张图片
AFN 3.0学习总结(四)_第4张图片
AFN 3.0学习总结(四)_第5张图片

来看看实现部分

AFN 3.0学习总结(四)_第6张图片

这里对两个属性进行的初始化操作,下面说一下NSIndexSet这个集合

NSIndexSet定义:是一个有序的,唯一的,无符号整数的集合。

先看个列子:

AFN 3.0学习总结(四)_第7张图片

打印结果如下:

AFN 3.0学习总结(四)_第8张图片

这充分说明了NSIndexSet的以下几点特征:

是有序的、唯一的、无符号整数合集。

因此下面定义的状态码为200~299

self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];

- (BOOL)validateResponse:(NSHTTPURLResponse *)response

                    data:(NSData *)data

                  error:(NSError * __autoreleasing *)error

{

    //1、默认responseIsValid = YES

    BOOL responseIsValid = YES;

    NSError *validationError = nil;

    //2、假如response存在,且类型是NSHTTPURLResponse

    if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {

        //2.1 self.acceptableContentTypes存在,但是不包含服务器返回的MIMEType,并且MIMEType和data都不能为nil

        if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&

            !([response MIMEType] == nil && [data length] == 0)) {

            if ([data length] > 0 && [response URL]) {

                //2.1.1生成错误信息

                NSMutableDictionary *mutableUserInfo = [@{

                                                          NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],

                                                          NSURLErrorFailingURLErrorKey:[response URL],

                                                          AFNetworkingOperationFailingURLResponseErrorKey: response,

                                                        } mutableCopy];

                //2.1.2 包含data的错误信息

                if (data) {

                    mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;

                }

                //2.1.3生成NSError

                validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);

            }

            responseIsValid = NO;

        }

        //同上

        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;

            }

            //设置错误,通过AFErrorWithUnderlyingError设置validationError的NSUnderlyingErrorKey

            validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);

            responseIsValid = NO;

        }

    }

    //赋值

    if (error && !responseIsValid) {

        *error = validationError;

    }

    return responseIsValid;

}

这个方法是检测响应的有效性的,这里主要对NSError进行以下介绍。

NSError有个属性NSDictionary *userInfo,错误信息一般都可以从这个字典中获得,因为是一个字典,所以我们会用到很多的key,我们看看系统自带的key和含义都有哪些

AFN 3.0学习总结(四)_第9张图片
AFN 3.0学习总结(四)_第10张图片

当然我们也可以自定义key来操作NSError。

在上面的那个方法中,有可能会出现两个错误,self.acceptableContentTypes和self.acceptableStatusCodes

这两个错误如果都出现了,怎么办?这里就用到了NSUnderlyingErrorKey这个字段,它表示一个优先的错误,value为NSError对象。通过下面这两个函数进行赋值和转换。

AFN 3.0学习总结(四)_第11张图片
AFN 3.0学习总结(四)_第12张图片

二、AFJSONResponseSerializer

AFN 3.0学习总结(四)_第13张图片

json的读取项目,默认为0,看看具体的定义

AFN 3.0学习总结(四)_第14张图片

1、NSJSONReadingMutableContainers json解析成功后返回一个容器

2、NSJSONReadingMutableLeaves 返回的json对象中的字符串为NSMutableString

3、NSJSONReadingAllowFragments 允许json字符串最外层既不是NSArray也不是NSDictonrary,但必须是有效的Json Fragment。例如使用这个选项可以解析@“123”这样的字符串

对NSJSONReadingMutableContainers进行举例说明

AFN 3.0学习总结(四)_第15张图片
AFN 3.0学习总结(四)_第16张图片

从初始化这里可以看出,AFN默认支持的Content-Type类型有@"application/json", @"text/json", @"text/javascript"

- (id)responseObjectForResponse:(NSURLResponse *)response

                          data:(NSData *)data

                          error:(NSError *__autoreleasing *)error

{

    //判断处理,如果验证结果失败,在没有error或者 错误中code:NSURLErrorCannotDecodeContentData的情况下,是不能解析数据的,直接返回nil

    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {

        if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {

            return nil;

        }

    }

    // Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.

    // See https://github.com/rails/rails/issues/1742

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

            *error = AFErrorWithUnderlyingError(serializationError, *error);

        }

        return nil;

    }


    if (self.removesKeysWithNullValues) {

        return AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);

    }

    return responseObject;

}

上面是json转换的核心方法,很好理解,里面只出现了两个函数,具体如下://检测错误或者优先错误中是否匹配code以及domain

static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {

    if ([error.domain isEqualToString:domain] && error.code == code) {

        return YES;

    } else if (error.userInfo[NSUnderlyingErrorKey]) {

        return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);

    }

    return NO;

}

static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {

//1、数组

if ([JSONObject isKindOfClass:[NSArray class]]) {

//创建了一个NSMutableArray,为了提高性能,使用了Capacity创建

NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];

//遍历数组,通过递归方式删除对象中的NSNull

for (id value in (NSArray *)JSONObject) {

[mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)]; }

//如果readingOptions==NSJSONReadingMutableContainers,返回可变的数组,否则是不可变的 return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];

}

else if ([JSONObject isKindOfClass:[NSDictionary class]]) {

//意思同上 NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];

for (id key in [(NSDictionary *)JSONObject allKeys]) {

            id value = (NSDictionary *)JSONObject[key];

            if (!value || [value isEqual:[NSNull null]]) {

                [mutableDictionary removeObjectForKey:key];

            } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {

                mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);

            }

        }

        return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];

    }

    return JSONObject;

}

三、AFXMLParserResponseSerializer

AFN 3.0学习总结(四)_第17张图片

AFN默认支持的xml类型为@"application/xml", @"text/xml"

四、AFXMLDocumentResponseSerializer

这个类只在mac os x上使用,不过多介绍

五、AFPropertyListResponseSerializer

这个类把json转换成PropertyList,支持的Content-Type类型为application/x-plist

六、UIImage (AFNetworkingSafeImageLoading)

这个是UIImage的分类

AFN 3.0学习总结(四)_第18张图片
AFN 3.0学习总结(四)_第19张图片

这里对image.images这个属性进行简单说明

UIImage *image0 = [UIImage imageNamed:@"SenderVoiceNodePlaying001"]; UIImage *image1 = [UIImage imageNamed:@"SenderVoiceNodePlaying002"]; UIImage *image2 = [UIImage imageNamed:@"SenderVoiceNodePlaying003"]; self.imageView.image = [UIImage animatedImageWithImages:@[image0,image1,image2] duration:1.5];

效果如下:

AFN 3.0学习总结(四)_第20张图片

可以看到,类似一个GIF图片的效果。

//根据响应结果,和scale返回一张图片

static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale) {

    if (!data || [data length] == 0) {

        return nil;

    }

    //只要是CG开头的,都是CoreGraphics

    CGImageRef imageRef = NULL;


    //CGDataProviderRef可以理解为是对data的包装,用完要进行释放

    CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);

    //根据返回的类型,初始化imageRef

    if ([response.MIMEType isEqualToString:@"image/png"]) {

        imageRef = CGImageCreateWithPNGDataProvider(dataProvider,  NULL, true, kCGRenderingIntentDefault);

    } else if ([response.MIMEType isEqualToString:@"image/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;

        }

    }

    //代码走到这里imageRef肯定是有值的,要么是根据response.MIMEType得到的,要么是根据image得到的

    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;

}

上面做这个转化的目的是为了尽量避免在主线程解压数据,因图像太大造成内存崩溃的问题。

七、AFImageResponseSerializer

AFN 3.0学习总结(四)_第21张图片

八、AFCompoundResponseSerializer

AFN 3.0学习总结(四)_第22张图片

这个符合类型有一个数组属性,内容是多种序列化的类型。由实现方法中也可以看出,遍历序列化类型,只要能转化成功,就返回数据。

九、总结

1、通过一个协议来得到不同的转换结果

2、知道AFN响应结果支持的各种类型

3、接触到了NSIndexSet的用法

4、如何创建一个NSError和一个带有优先错误的NSUnderlyingErrorKey

5、服务器返回的图片是压缩格式,要进行压缩

6、使用images来实现gif效果

其它的文章的总结

AFJSONResponseSerializer使用系统内置的NSJSONSerialization解析json,NSJSON只支持解析UTF8编码的数据(还有UTF-16LE之类的,都不常用),所以要先把返回的数据转成UTF8格式。这里会尝试用HTTP返回的编码类型和自己设置的stringEncoding去把数据解码转成字符串NSString,再把NSString用UTF8编码转成NSData,再用NSJSONSerialization解析成对象返回。

上述过程是NSData->NSString->NSData->NSObject,这里有个问题,如果你能确定服务端返回的是UTF8编码的json数据,那NSData->NSString->NSData这两步就是无意义的,而且这两步进行了两次编解码,很浪费性能,所以如果确定服务端返回utf8编码数据,就建议自己再写个JSONResponseSerializer,跳过这两个步骤。

此外AFJSONResponseSerializer专门写了个方法去除NSNull,直接把对象里值是NSNull的键去掉,还蛮贴心,若不去掉,上层很容易忽略了这个数据类型,判断了数据是否nil没判断是否NSNull,进行了错误的调用导致core。

图片解压

当我们调用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可以做这个事。

关于图片解压,还有几个问题不清楚:

1.本来以为调用imageWithData方法只是持有了数据,没有做解压相关的事,后来看到调用堆栈发现已经做了一些解压操作,从调用名字看进行了huffman解码,不知还会继续做到解码jpg的哪一步。 

UIImage_jpg

2.以上图片手动解压方式都是在CPU进行的,如果不进行手动解压,把图片放进layer里,让底层自动做这个事,是会用GPU进行的解压的。不知用GPU解压与用CPU解压速度会差多少,如果GPU速度很快,就算是在主线程做解压,也变得可以接受了,就不需要手动解压这样的优化了,不过目前没找到方法检测GPU解压的速度。

你可能感兴趣的:(AFN 3.0学习总结(四))