当前位置:文档之家› [无限互联]AFNetworking源码之Serialization模块

[无限互联]AFNetworking源码之Serialization模块

[无限互联]AFNetworking源码之Serialization模块
[无限互联]AFNetworking源码之Serialization模块

[无限互联]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"> //调用AFQueryStringFromParametersWithEncoding方法对传进来的数据进行转换

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模块就讲到这了

相关主题
文本预览
相关文档 最新文档