[无限互联]AFNetworking源码之Serialization模块
本篇我们来看AFNetworking的下一个模块Serialization
其中包括AFURLRequestSerialization和AFURLResponseSerialization两个类
我们主要讲AFURLRequestSerialization,因为AFURLRequestSerialization的实现比AFURLResponseSerialization复杂得多,我们理
解了AFURLRequestSerialization就不难理解AFURLResponseSerialization了。AFURLRequestSerialization的作用是协助构建NSURLRequest
其主要实现以下两个功能:
1.构建普通请求:格式化请求参数,生成HTTP Header。
2.构建multipart请求。
1.构建普通请求
格式化请求参数
一般我们请求都会按key=value的方式带上各种参数,GET方法参数直接加在URL上,
POST方法放在body上,NSURLRequest没有封装好这个参数的解析,只能我们自己拼
好字符串。AFNetworking提供了接口,让参数可以是NSDictionary, NSArray, NSSet这
些类型,再由内部解析成字符串后赋给NSURLRequest。
转换分为三部分:
第一部分是用户传进来的数据,支持包含NSArray,NSDictionary,NSSet这三种数据结构。
第二部分是转换成AFNetworking内自己的数据结构,每一个key-value对都用一个对象AFQueryStringPair表示,作用是最后可以根据不同的字符串编码生成各自的key=value 字符串。主要函数是AFQueryStringPairsFromKeyAndValue。
第三部分是最后生成NSURLRequest可用的字符串数据,并且对参数进行url编码,在AFQueryStringFromParametersWithEncoding这个函数里。
最后在把数据赋给NSURLRequest时根据不同的HTTP方法分别处理,对于
GET/HEAD/DELETE方法,把参数加到URL后面,对于其他如POST/PUT方法,把数据加到body上,并设好HTTP头,告诉服务端字符串的编码。
相关代码:
[objc]view plaincopyprint?
1.//第一部分用户传进来的数据,parameters支持包含
NSArray,NSDictionary,NSSet这三种数据结构。
2.- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
3.withParameters:(id)parameters
4.error:(NSError *__autoreleasing *)error
5.{
6.NSParameterAssert(request);
7.
8.NSMutableURLRequest *mutableRequest = [request mutableCopy];
9.
10.//..........省略一些代码
11.if (parameters) {
12.NSString *query = nil;
13.if (self.queryStringSerialization) {
14.query = self.queryStringSerialization(request, parameters, error);
15.} else {
16.switch (self.queryStringSerializationStyle) {
17.case AFHTTPRequestQueryStringDefaultStyle: style="white-space:pre">
18.query = AFQueryStringFromParametersWithEncoding(parameters,
self.stringEncoding);
19.break;
20.}
21.}
//..........省略一些代码return mutableRequest;}
[objc]view plaincopyprint?
1.//对用户传进来的数据进行转换
2.static NSString *
AFQueryStringFromParametersWithEncoding(NSDictionary *parameters,
NSStringEncoding stringEncoding) {
3.NSMutableArray *mutablePairs = [NSMutableArray array];
4.for (AFQueryStringPair *pair in
AFQueryStringPairsFromDictionary(parameters)) {
5.//第二部分将用户传进来的参数转换成AFNetworking内自己的数据结构
6.[mutablePairs addObject:[pair
URLEncodedStringValueWithEncoding:stringEncoding]];
7.}
8.
9.//第三部分生成NSURLRequest可用的字符串数据,并且对参数进行url编码
10.return [mutablePairs componentsJoinedByString:@"&"];
11.}
12.
13.NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
14.return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
15.}
16.
17.//将用户传进来的参数转换成AFNetworking内自己的数据结构,每一个
key-value对都用一个对象AFQueryStringPair表示,作用是最后可以根据不同的字符串编码生成各自的key=value字符串。
18.NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
19.NSMutableArray *mutableQueryStringComponents = [NSMutableArray
array];
20.
21.NSSortDescriptor *sortDescriptor = [NSSortDescriptor
sortDescriptorWithKey:@"description" ascending:YES
selector:@selector(compare:)];
22.
23.if ([value isKindOfClass:[NSDictionary class]]) { //用户传进来的是
NSDictionary类型
24.NSDictionary *dictionary = value;
25.// Sort dictionary keys to ensure consistent ordering in query string, which is
important when deserializing potentially ambiguous sequences, such as an array of dictionaries
26.for (id nestedKey in [dictionary.allKeys
sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
27.id nestedValue = [dictionary objectForKey:nestedKey];
28.if (nestedValue) {
29.[mutableQueryStringComponents
addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ?
[NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
30.}
31.}
32.} else if ([value isKindOfClass:[NSArray class]]) { //用户传进来的数据是
NSArray类型
33.NSArray *array = value;
34.for (id nestedValue in array) {
35.[mutableQueryStringComponents
addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString
stringWithFormat:@"%@[]", key], nestedValue)];
36.}
37.} else if ([value isKindOfClass:[NSSet class]]) { //用户传进来的数据是NSSet类
型
38.NSSet *set = value;
39.for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
40.[mutableQueryStringComponents
addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
41.}
42.} else {
43.[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc]
initWithField:key value:value]];
44.}
45.
46.return mutableQueryStringComponents;
47.}
HTTP Header
AFNetworking帮你组装好了一些HTTP请求头,包括语言Accept-Language,根据[NSLocale preferredLanguages] 方法读取本地语言,高速服务端自己能接受的语言。还有构建User-Agent,以及提供Basic Auth 认证接口,帮你把用户名密码做base64 编码后放入HTTP 请求头。
相关代码:
[objc]view plaincopyprint?
1.//AFNetworking帮组装好了一些HTTP请求头,
包括语言Accept-Language,User-Agent等
2.- (instancetype)init {
3.self = [super init];
4.if (!self) {
5.return nil;
6.}
7.......
8.// Accept-Language HTTP Header; see
https://www.doczj.com/doc/4b18484134.html,/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
9.NSMutableArray *acceptLanguagesComponents = [NSMutableArray
array];
10.//根据[NSLocale preferredLanguages] 方法读取本地语言,高速服务端自己能
接受的语言
11.[[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj,
NSUInteger idx, BOOLBOOL *stop) {
12.float q = 1.0f - (idx * 0.1f);
13.[acceptLanguagesComponents addObject:[NSString
stringWithFormat:@"%@;q=%0.1g", obj, q]];
14.*stop = q <= 0.5f;
15.}];
16.[self setValue:[acceptLanguagesComponents
componentsJoinedByString:@", "]
forHTTPHeaderField:@"Accept-Language"];
17.NSString *userAgent = nil;
18.#pragma clang diagnostic push
19.#pragma clang diagnostic ignored "-Wgnu"
20.#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
21.// User-Agent Header; see
https://www.doczj.com/doc/4b18484134.html,/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
https://www.doczj.com/doc/4b18484134.html,erAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@;
Scale/%0.2f)", [[[NSBundle mainBundle] infoDictionary]
objectForKey:(__bridge NSString *)kCFBundleExecutableKey] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString
*)kCFBundleIdentifierKey], (__bridge
id)CFBundleGetValueForInfoDictionaryKey(CFBundleGetMainBundle(),
kCFBundleVersionKey) ?: [[[NSBundle mainBundle] infoDictionary]
objectForKey:(__bridge NSString *)kCFBundleVersionKey], [[UIDevice
currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
23.#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
https://www.doczj.com/doc/4b18484134.html,erAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)",
[[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleExecutableKey] ?: [[[NSBundle mainBundle] infoDictionary]
objectForKey:(__bridge NSString *)kCFBundleIdentifierKey], [[[NSBundle mainBundle] infoDictionary]
objectForKey:@"CFBundleShortVersionString"] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
25.#endif
26.#pragma clang diagnostic pop
27.if (userAgent) {
28.if (![userAgent canBeConvertedT oEncoding:NSASCIIStringEncoding]) {
29.NSMutableString *mutableUserAgent = [userAgent mutableCopy];
30.if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent),
NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
https://www.doczj.com/doc/4b18484134.html,erAgent = mutableUserAgent;
32.}
33.}
34.[self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
35.}
36.// HTTP Method Definitions; see
https://www.doczj.com/doc/4b18484134.html,/Protocols/rfc2616/rfc2616-sec9.html
37.self.HTTPMethodsEncodingParametersInURI = [NSSet
setWithObjects:@"GET", @"HEAD", @"DELETE", nil nil];
38.return self;
39.}
?其他格式化方式
HTTP请求参数不一定是要key=value形式,可以是任何形式的数据,可以是json格式,苹果的plist格式,二进制protobuf格式等,AFNetworking提供了方法可以很容易扩展支持这些格式,默认就实现了json和plist格式。详情见源码的类AFJSONRequestSerializer和AFPropertyListRequestSerializer。
2.构建multipart请求
构建Multipart请求是占篇幅很大的一个功能,AFURLRequestSerialization里2/3的代码都是在做这个事。
?Multipart协议介绍
Multipart是HTTP协议为web表单新增的上传文件的协议,协议文档是rfc1867,它基于HTTP的POST方法,数据同样是放在body上,跟普通POST方法的区别是数据不是
key=value形式,key=value形式难以表示文件实体,为此Multipart协议添加了分隔符,有自己的格式结构。
实现
接下来说说怎样构造Multipart里的数据,最简单的方式就是直接拼数据,要发送一个文件,就直接把文件所有内容读取出来,再按上述协议加上头部和分隔符,拼接好数据后扔给NSURLRequest的body就可以发送了,很简单。但这样做是不可用的,因为文件可能很大,这样拼数据把整个文件读进内存,很可能把内存撑爆了。
第二种方法是不把文件读出来,不在内存拼,而是新建一个临时文件,在这个文件上拼接数据,再把文件地址扔给NSURLRequest的bodyStream,这样上传的时候是分片读取这个文件,不会撑爆内存,但这样每次上传都需要新建个临时文件,对这个临时文件的管理也挺麻烦的。
第三种方法是构建自己的数据结构,只保存要上传的文件地址,边上传边拼数据,上传是分片的,拼数据也是分片的,拼到文件实体部分时直接从原来的文件分片读取。这方法没上述两种的问题,只是实现起来也没上述两种简单,AFNetworking就是实现这第三种方法,而且还更进一步,除了文件,还可以添加多个其他不同类型的数据,包括NSData,和InputStream。
AFNetworking 里multipart 请求的使用方式是这样:
[objc]view plaincopyprint?
1. /*
2.urlstring:字符串型的链接
3.params:请求参数
4.datas:数组里存得是上传的NSdata数据
5.*/
6.AFHTTPRequestOperationManager *manager =
[AFHTTPRequestOperationManager manager];
7.[manager POST:urlstring parameters:params
constructingBodyWithBlock:^(id
formData) { 8.//将需要上传的文件数据添加到formData
9.//循环遍历需要上的文件数据
10.for (NSString *name in datas) {
11.NSData *data = datas[name];
12.[formData appendPartWithFileData:data name:name fileName:name
mimeType:@"image/jpeg"];
13.}
14.} success:^(AFHTTPRequestOperation *operation, id responseObject) {
15.block(responseObject);
16.} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
17.NSLog(@"网络请求失败:%@",error);
18.}];
这里通过constructingBodyWithBlock向使用者提供了一个AFStreamingMultipartFormData对象,调这个对象的几种append方法就可以添加不同
类型的数据,包括FileURL/NSData/NSInputStream,AFStreamingMultipartFormData 内部把这些append的数据转成不同类型的AFHTTPBodyPart,添加到自定义的AFMultipartBodyStream 里。最后把AFMultipartBodyStream 赋给原来NSMutableURLRequest的bodyStream。NSURLConnection 发送请求时会读取这个bodyStream,在读取数据时会调用这个bodyStream 的-read:maxLength: 方法,AFMultipartBodyStream 重写了这个方法,不断读取之前append进来的AFHTTPBodyPart 数据直到读完。
AFHTTPBodyPart 封装了各部分数据的组装和读取,一个AFHTTPBodyPart 就是一个数据块。实际上三种类型(FileURL/NSData/NSInputStream) 的数据在AFHTTPBodyPart 都转成NSInputStream,读取数据时只需读这个inputStream。inputStream 只保存了数据的实体,没有包括分隔符和头部,AFHTTPBodyPart 是边读取变拼接数据,用一个状态机确定现在数据读取到哪一部份,以及保存这个状态下已被读取的字节数,以此定位要读的数据位置,详见AFHTTPBodyPart 的-read:maxLength:方法。AFMultipartBodyStream封装了整个multipart数据的读取,主要是根据读取的位置确定现在要读哪一个AFHTTPBodyPart。AFStreamingMultipartFormData对外提供友好的append接口,并把构造好的AFMultipartBodyStream赋回给NSMutableURLRequest。详情请看在AFURLRequestSerialization类中定义的AFHTTPBodyPart类、AFMultipartBodyStream、AFStreamingMultipartFormData。
仅仅列举几段代码:
[objc]view plaincopyprint?
1./*
2.NSURLConnection 发送请求时会读取这个bodyStream,在读取数据时会调用这
个bodyStream 的
3.-read:maxLength: 方法,AFMultipartBodyStream 重写了这个方法,不断读取
之前append进来
4.的AFHTTPBodyPart 数据直到读完。
5.FMultipartBodyStream 重写了这个方法,不断读取之前append进来的
AFHTTPBodyPart 数据直到读完。
6.*/
7.- (NSInteger)read:(uint8_t *)buffer
8.maxLength:(NSUInteger)length
9.{
10.if ([self streamStatus] == NSStreamStatusClosed) {
11.return 0;
12.}
13.NSInteger totalNumberOfBytesRead = 0;
14.#pragma clang diagnostic push
15.#pragma clang diagnostic ignored "-Wgnu"
16.while ((NSUInteger)totalNumberOfBytesRead < MIN(length,
self.numberOfBytesInPacket)) {
17.if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart
hasBytesAvailable]) {
18.if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator
nextObject])) {
19.break;
20.}
21.} else {
22.NSUInteger maxLength = length - (NSUInteger)totalNumberOfBytesRead;
23.NSInteger numberOfBytesRead = [self.currentHTTPBodyPart
read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
24.if (numberOfBytesRead == -1) {
25.self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
26.break;
27.} else {
28.totalNumberOfBytesRead += numberOfBytesRead;
29.if (self.delay > 0.0f) {
30.[NSThread sleepForTimeInterval:self.delay];
31.}
32.}
33.}
34.}
35.#pragma clang diagnostic pop
36.return totalNumberOfBytesRead;
37.}
[objc]view plaincopyprint?
1.//添加NSInputStream类型数据
2.- (void)appendPartWithInputStream:(NSInputStream *)inputStream
https://www.doczj.com/doc/4b18484134.html,:(NSString *)name
4.fileName:(NSString *)fileName
5.length:(int64_t)length
6.mimeType:(NSString *)mimeType
7.{
8.NSParameterAssert(name);
9.NSParameterAssert(fileName);
10.NSParameterAssert(mimeType);
11.NSMutableDictionary *mutableHeaders = [NSMutableDictionary
dictionary];
12.[mutableHeaders setValue:[NSString stringWithFormat:@"form-data;
name=\"%@\"; filename=\"%@\"", name, fileName]
forKey:@"Content-Disposition"];
13.[mutableHeaders setValue:mimeType forKey:@"Content-Type"];
14.//把这些append进来的数据转成不同类型的AFHTTPBodyPart
15.AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
16.bodyPart.stringEncoding = self.stringEncoding;
17.bodyPart.headers = mutableHeaders;
18.bodyPart.boundary = self.boundary;
19.bodyPart.body = inputStream;
20.bodyPart.bodyContentLength = (unsigned long long)length;
21.//添加bodyPart 到自定义的AFMultipartBodyStream 里
22.[self.bodyStream appendHTTPBodyPart:bodyPart];
23.}
3.AFURLResponseSerialization
AFURLResponseSerialization主要的功能是处理返回数据,告诉AFNetworking 以怎样的方式接受数据,如果后段接口都是标准的JSON数据格式,那么很愉快的就选择了AFJSONResponseSerializer,在请求成功的Block中的responseObject就会是一个AFNetworking 帮你解好档的JSON,也就是一个NSDictionary对象。
AFURLResponseSerialization对数据的处理有以下几个方式
?AFHTTPResponseSerializer
?AFJSONResponseSerializer
?AFXMLParserResponseSerializer
?AFXMLDocumentResponseSerializer (Mac OS X 中)
?AFPropertyListResponseSerializer
?AFImageResponseSerializer
?AFCompoundResponseSerializer
[objc]view plaincopyprint?
1.//HTTP解析
2.+ (instancetype)serializer;
3.//JSON解析
4.+
(instancetype)serializerWithReadingOptions:(NSJSONReadingOptions)readingO ptions;
5.//XML解析
6.+ (instancetype)serializerWithXMLDocumentOptions:(NSUInteger)mask;
7.//Plist解析
8.+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
9.readOptions:(NSPropertyListReadOptions)readOptions;
10.//Compound解析
11.+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray
*)responseSerializers;
Serialization模块就讲到这了
相关主题文本预览