续AFNetworking核心类AFURLConnectionOperation的详解
本篇我们来看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 plaincopy
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:<span style="white-space:pre"> </span>//调用<span style="font-family: 'Microsoft YaHei';">AFQueryStringFromParametersWithEncoding方法对传进来的数据进行转换</span>
18. query = AFQueryStringFromParametersWithEncoding(parameters, self.stringEncoding);
19. break;
20. }
21. }<pre name="code" class="objc" style="color: rgb(37, 37, 37); font-size: 14px; line-height: 28px;">//..........省略一些代码
return mutableRequest;}
[objc] view plaincopy
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 plaincopy
1. <span style="font-size:14px;">//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 http://www.w3.org/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 http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
22. userAgent = [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)
24. userAgent = [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 canBeConvertedToEncoding:NSASCIIStringEncoding]) {
29. NSMutableString *mutableUserAgent = [userAgent mutableCopy];
30. if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
31. userAgent = mutableUserAgent;
32. }
33. }
34. [self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
35. }
36. // HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
37. self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil nil];
38. return self;
39. }</span>
• 其他格式化方式
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 plaincopy
1. <span style="font-size:14px;"> /*
2. urlstring:字符串型的链接
3. params:请求参数
4. datas:数组里存得是上传的NSdata数据
5. */
6. AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
7. [manager POST:urlstring parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> 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. }];</span>
这里通过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 plaincopy
1. <span style="font-size:14px;">/*
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. }</span>
[objc] view plaincopy
1. <span style="font-size:14px;">//添加NSInputStream类型数据
2. - (void)appendPartWithInputStream:(NSInputStream *)inputStream
3. name:(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. }</span>
3.AFURLResponseSerialization
AFURLResponseSerialization主要的功能是处理返回数据,告诉AFNetworking 以怎样的方式接受数据,如果后段接口都是标准的JSON数据格式,那么很愉快的就选择了AFJSONResponseSerializer,在请求成功的Block中的responseObject就会是一个 AFNetworking 帮你解好档的JSON,也就是一个NSDictionary对象。
AFURLResponseSerialization对数据的处理有以下几个方式
• AFHTTPResponseSerializer
• AFJSONResponseSerializer
• AFXMLParserResponseSerializer
• AFXMLDocumentResponseSerializer ( Mac OS X 中)
• AFPropertyListResponseSerializer
• AFImageResponseSerializer
• AFCompoundResponseSerializer
[objc] view plaincopy
1. //HTTP解析
2. + (instancetype)serializer;
3. //JSON解析
4. + (instancetype)serializerWithReadingOptions:(NSJSONReadingOptions)readingOptions;
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模块就讲到这了