AFNetworking框架分析(四)——请求的序列化AFURL
之前用了两篇篇幅分析了下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.支持表单结构数据以数据流拼接分片上传