AFNetworking框架分析(四)——请求的序列化AFURLRequestSerialization分析

之前用了两篇篇幅分析了下AFN的核心类AFURLSessionManager在网络请求之前、请求中、以及请求结束时,做了哪些工作。接下来,将用两篇文章的篇幅来分析一下AFN中网络请求AFURLRequestSerialization与响应AFURLResponseSerialization的序列化。

首先是AFURLRequestSerialization请求的序列化。
查看AFURLRequestSerialization的头文件中,实现了AFURLRequestSerialization协议,协议中只有一个方法- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(nullable id)parameters error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;,用于返回一个原始request的copy对象,将参数根据指定的编码格式进行处理。
AFHTTPRequestSerializer作为请求序列化的一个根类,作为AFN的默认设置。AFJSONRequestSerializerAFPropertyListRequestSerializer作为子类都继承自AFHTTPRequestSerializer。
头文件中还存在AFMultipartFormData协议,主要用于多部分表单的处理,之后将以表单形式POST请求为例,来分析其中的工作流程。
AFURLRequestSerialization协议,继承自<NSObject, NSSecureCoding, NSCopying>三个协议。其中NSSecureCoding协议,主要用于在解码时要同时指定key和要解码的对象的类,如果要求的类和从文件中解码出的对象的类不匹配,NSCoder则会抛出异常并通知数据已经被篡改。NSCopying协议是为了能够让当前类支持拷贝功能。
以POST请求为例,提交的数据都是放到请求体body中,但并未规定编码方式,那么就需要设置Content-Type告知后台服务数据的格式。

数据格式

简单基本的网络请求过程,之前已经介绍过。在实际开发中避免不了与后台大文件传输,那么就要将上传或下载的大文件以数据流的形式进行传输。此时就需要用到AFN框架中的- (NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(id)parameters constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block progress:(nullable void (^)(NSProgress * _Nonnull))uploadProgress success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure方法,与AFN中基本的POST方法相比,多声明了一处参数constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block。看看它又多做了什么处理。

实现方法

可以发现,通过声明一个AFMultipartFormData类型的formData来构建用于multipartForm请求体request。

- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
                                              URLString:(NSString *)URLString
                                             parameters:(NSDictionary *)parameters
                              constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
                                                  error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(method);
    NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]);

    NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];

    __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];

    if (parameters) {
        for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
            NSData *data = nil;
            if ([pair.value isKindOfClass:[NSData class]]) {
                data = pair.value;
            } else if ([pair.value isEqual:[NSNull null]]) {
                data = [NSData data];
            } else {
                data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
            }

            if (data) {
                [formData appendPartWithFormData:data name:[pair.field description]];
            }
        }
    }

    if (block) {
        block(formData);
    }

    return [formData requestByFinalizingMultipartFormData];
}

实现方法中,首先用断言判断了GET与HEAD类型的请求不能继续执行后续代码。在创建了mutableRequest之后,为了构建bodyStream,初始化了一个AFStreamingMultipartFormData类型的对象,并用__block修饰。在其init方法中,分别声明了实例变量请求request、字符串编码格式stringEncoding、分隔符boundary以及数据流bodyStream。
这里扩展一下,AFMultipartBodyStream类中声明了NSInputStream类型的对象。而NSInputStream是文件的读取流,是将本地的文件读取到内存中去 ,与之对应的就是NSOutputStream,文件的写入流,将内存中的文件数据写入到文件中。继续深入的话,网络请求都是基于coreFoundation的cfnetwork,而文件的读、写流,也分别对应着coreFoundation中的CFWriteStreamRef与CFWriteStreamRef相关的C函数。可以查看CoreFoundation框架中的CFStream头文件


CFStream头文件C函数方法

AFN中定义的分隔符方法,使用两个十六进制随机数拼接在Boundary后面来表示分隔符

static NSString * AFCreateMultipartFormBoundary() {
    return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
}

创建完成AFStreamingMultipartFormData对象后,接下来的操作与基本POST请求相同,遍历parameters参数字典,将其转换成NSData并拼接进之前的AFStreamingMultipartFormData对象中。
而构造bodyStream最主要的实现,就在[formData appendPartWithFormData:data name:[pair.field description]]这行代码中,根据data和name来构建request的header与body。
在方法实现中,拼接成符合表单传输的格式,并添加至实例变量bodyStream中,也就是对应的body数据。

表单格式的数据结构示例图

接下来的,执行block(formData)代码块,就可以在代码实现的block中将图片添加至formData。
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block添加本地图片实现方法:

NSString *imgPath = [[NSBundle mainBundle] pathForResource:@"upload" ofType:@"png"];
[formData appendPartWithFileURL:[NSURL fileURLWithPath:imgPath] name:@"(后台指定的key名)" error:nil];

添加图片至body数据流中的实现方法,首先利用文件扩展名和C函数获取UTI统一类型标志符,再根据UTI获取contentType。

static inline NSString * AFContentTypeForPathExtension(NSString *extension) {
    NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
    NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
    if (!contentType) {
        return @"application/octet-stream";
    } else {
        return contentType;
    }

接着,检查fileURL是否合法以及文件是否存在。若文件存在,创建一个AFHTTPBodyPart对象,拼接成符合表单数据结构的字典并放入该对象的header中,完成后将AFHTTPBodyPart对象添加至body数据对象bodyStream。


表单中添加图片文件后的数据结构

走到这一步,表单中的参数拼接已经完成。但一个完整的表单格式的请求参数,还缺少基本的信息,而保证这些信息的完整性,最后由[formData requestByFinalizingMultipartFormData]方法实现,在表单的首尾添加分隔符、设置request的bodyStream为self.bodyStream(而非setBody方法)、设置Content-Type、设置Content-Length这四步操作。

完整的表单数据信息

针对表单形式的POST请求,request的初始化已经完成。之后task任务创建与处理,与普通的POST请求无异。
AFN框架在表单形式的POST请求中,帮我们做了添加分隔符、并将所有的传参data拼接在一起,作为一个完整的请求数据流发送给服务器等一系列工作。

这一篇通过举例较为复杂而且经典的表单形式POST请求,可以总结出AFURLRequestSerialization类的作用。
1.使用KVO以及KVC来动态监听并修改request属性
2.设置request的请求header
3.生成请求参数查询字符串
4.支持表单结构数据以数据流拼接分片上传


该文章首次发表在 简书:我只不过是出来写写代码 博客,并自动同步至 腾讯云:我只不过是出来写写iOS 博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容