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的默认设置。AFJSONRequestSerializer
、AFPropertyListRequestSerializer
作为子类都继承自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.支持表单结构数据以数据流拼接分片上传
- 缓慢的update语句性能分析(r6笔记第61天)
- 一个dg警告发现的硬件问题 (r6笔记第60天)
- mysql几种存储引擎介绍
- Java基础-21(02)总结字符流,IO流编码问题,实用案例必做一遍
- DeDeCMS v5.7 密码修改漏洞分析
- Java基础-20(01)总结,递归,IO流
- 一个Oracle bug的手工修复(r6笔记第59天)
- 由drop datafile导致的oracle bug(r6笔记第56天)
- Java中static关键字的作用
- Java基础-20(02)总结,递归,IO流
- Hive四种数据导入方式
- 34c3 部分Web Writeup
- 原来Oracle也不喜欢“蜀黍"(r6笔记第54天)
- Java基础19(01)总结IO流,异常try…catch,throws,File类
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 掌握好这几个css属性,少写100行js代码
- Flash写入性能下降问题
- 如何用开源项目申请 JetBrains 产品的 license
- npm -i 与npm install -S与-D的区别以及dependencies与devDependencies的区别
- axios POST提交数据的三种请求方式写法
- 将资源文件编译成源代码文件
- 一道简单的笔试题_时钟切换电路(Glitch-free clock switching circuit)
- 【STM32F429开发板用户手册】第26章 STM32F429的定时器应用之TIM1-TIM14的中断实现
- 【STM32F407开发板用户手册】第26章 STM32F407的定时器应用之TIM1-TIM14的中断实现
- ATTR节点应用
- 等待队列
- samba
- gpio_key按键驱动
- Linux中断下半部实现机制
- linux中led驱动(设备树)编程笔记