前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AFNetworking框架分析(五)——响应的序列化AFURLResponseSerialization分析

AFNetworking框架分析(五)——响应的序列化AFURLResponseSerialization分析

作者头像
我只不过是出来写写代码
发布2019-04-02 14:57:17
1.5K1
发布2019-04-02 14:57:17
举报

这一篇将分析网络请求收到数据时的响应AFURLResponseSerialization序列化过程。 当AFURLRequestSerialization类将所有的请求数据处理完成发送请求之后,当收到返回的数据信息时,这时就要靠AFURLResponseSerialization类来完成不同类型返回数据的序列化操作。 从AFURLResponseSerialization头文件中,可以看出与AFURLRequestSerialization类的结构非常相似。从上往下,首先声明了AFURLResponseSerialization协议,协议中只有一个方法,将response解码成指定的相关数据,这是所有响应类都需要遵循的协议。之后声明了一个AFHTTPResponseSerializer类,作为响应类的根类。再往下的类,都是继承自AFHTTPResponseSerializer的子类,分别是AFJSONResponseSerializer(JSON格式数据响应,默认)、AFXMLParserResponseSerializer(iOS端XML数据解析响应)、AFXMLDocumentResponseSerializer(MAC OS端XML数据解析响应)、AFPropertyListResponseSerializer(PList格式数据解析响应)、AFImageResponseSerializer(图片数据解析响应)和AFCompoundResponseSerializer(复合式数据解析响应) 在父类AFHTTPResponseSerializer中,遵循的协议方法不做任何事情 只做一次response的验证。实现方法中,只有[self validateResponse:(NSHTTPURLResponse *)response data:data error:error]验证response是否合规的方法。而且初始化init方法中,父类只是设置编码格式为UTF-8,设置http状态码为200-299,表示只有这些状态码获得了有效的响应,而不在接受范围内的状态码和内容类型会在数据解析时发生错误。而且其中一句代码self.acceptableContentTypes = nil;,本身acceptableContentTypes用于设置可接受的contentType,这里置为nil,也从侧面建议不要直接使用父类。

父类AFHTTPResponseSerializer初始化方法

所以,当需要响应具体不同类型的数据序列化操作时,都是由其对应的子类来完成任务。

- (BOOL)validateResponse:(NSHTTPURLResponse *)response
                    data:(NSData *)data
                   error:(NSError * __autoreleasing *)error
{
    BOOL responseIsValid = YES;
    NSError *validationError = nil;

    if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
        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 = 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;
            }

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

            responseIsValid = NO;
        }
    }

    if (error && !responseIsValid) {
        *error = validationError;
    }

    return responseIsValid;
}

该验证方法中,默认设置返回BOOL值为YES,后续只处理数据无效的各种情况。首先,根据初始化的acceptableContentTypes 判断MIME媒体类型是否合法;其次,根据初始化的acceptableStatusCodes 判断状态码是否有效。 代码实现中,NSLocalizedDescriptionKey是NSError头文件中预定义的键,标识错误的本地化描述.可以通过NSError的localizedDescription方法获得对应的值信息,而NSURLErrorFailingURLErrorKey相应的值是包含导致加载失败的URL的NSURL。生成错误信息字典,会返回unacceptable content-type的信息,并将错误信息记录在了mutableUserInfo中。 因此,如果content-type不满足,那么产生的validationError就是Domain为AFURLResponseSerializationErrorDomain,code为NSURLErrorCannotDecodeContentData;如果MIME type不满足,那么产生的validationError就是Domain为AFURLResponseSerializationErrorDomain,code为NSURLErrorBadServerResponse。 这里需要注意一个error处理逻辑,不管是判断媒体类型还是状态码,都用到了validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);函数。查看此函数的实现,可以发现AFN已经处理好了当两种错误同时出现的情况以及优先级显示。将媒体类型的error信息放入至状态码error中userInfo字典的NSUnderlyingErrorKey值中去。

// 设置一个underlyingError为error的附属
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
   // 是否传入了error
    if (!error) {
        return underlyingError;
    }
    // 是否已经有附属
    if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
        return error;
    }
    // 取出error的userInfo
    NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
    mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;

    return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}

以JSON格式的数据为例,当响应到JSON格式的数据时,就需要AFJSONResponseSerializer子类去完成response序列化工作。 首先在初始化方法init中,设置了acceptableContentTypes的集合内容self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];(小提示:json格式是 js 代码的一个子集。也就是说 json 格式的数据,也是 js 代码,也会被浏览器的js引擎执行,从而生成 json 对象) 接下来,AFJSONResponseSerializer类遵循的协议方法会对JSON格式的数据进行删除空数据处理,利用遍历与递归将value值为空的key进行删除操作。类似的,AFXMLParserResponseSerializer、AFXMLDocumentResponseSerializer、AFPropertyListResponseSerializer都是将返回数据进行对应格式类型的数据转换,并删除其中无效的key,最终返回出response。AFCompoundResponseSerializer类型的,会进行所有支持数据类型的遍历,以匹配哪种类型的数据可以进行数据解析。

这里单独拿出AFImageResponseSerializer类进行分析,此类用于接收处理图片类型的数据。在解析图片数据时,用到了函数static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale)来手动解压图片。根据response和scale(scale大小为屏幕宽高)转换为位图bitmap,这里因为解码的过程都是在网络请求回来调用的,避免了系统将在主线程进行解码,从而显示图片的时候直接绘制,节省GPU开销。此函数中主要涉及到了CoreGraphics内容。 对CoreGraphics有兴趣了解的,可以看下阿里云对其介绍链接在此 首先将图片data封装至CGDataProviderRef对象中,然后只针对jpg与png格式的图片数据来单独给CGImageRef对象赋值,以此来创建CGImage用于表示data中的图片是压缩格式。在jpg格式中,AFN单独判断了CMKY类型的图片不支持转换为位图

            // CGImageCreateWithJPEGDataProvider does not properly handle CMKY, so fall back to AFImageWithDataAtScale
            if (imageColorSpaceModel == kCGColorSpaceModelCMYK) {
                CGImageRelease(imageRef);
                imageRef = NULL;
            }

接下来,根据图片data数据创建一个UIImage对象,然后根据上面的CGImageRef对象判断是否为压缩格式图片。若非压缩格式图片且不为空,则直接把原图片返回出去,为空时直接返回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;

按照个人理解,首先是通过CoreGraphics框架来获取图片CGImageRef对象的宽高、以及每个颜色的比特数。当宽高像素大于1024*1024像素,或者每个颜色的比特数大于8时,表明图片过大直接返回原图出去。接下来,获取到图片的各种信息,来创建一个CGContextRef类型对象context,也就是bitmap的上下文。继续把context渲染到画布上,根据context生成一个bitmap格式的图片。然后将图片转换成UIImage格式的图片作为response数据返回给AFURLSessionManager类。最终通过block返回出图片数据。 小插曲:Apple官方更推荐使用png格式的压缩图片进行网络传输返回至手机端。pngcrush工具,了解一下,可以更快速地解压与渲染图片,节省系统资源。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.06.22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档