转载AFNetworking源码解析(二)

来源:互联网 发布:长沙网络招工 编辑:程序博客网 时间:2024/06/16 06:40

AFNetworking2.0源码解析<二>

2014-9-3

续AFNetworking2.0源码解析<一>

本篇我们继续来看看AFNetworking的下一个模块 — AFURLRequestSerialization。

AFURLRequestSerialization用于帮助构建NSURLRequest,主要做了两个事情:
1.构建普通请求:格式化请求参数,生成HTTP Header。
2.构建multipart请求。
分别看看它在这两点具体做了什么,怎么做的。

1.构建普通请求

A.格式化请求参数

一般我们请求都会按key=value的方式带上各种参数,GET方法参数直接加在URL上,POST方法放在body上,NSURLRequest没有封装好这个参数的解析,只能我们自己拼好字符串。AFNetworking提供了接口,让参数可以是NSDictionary, NSArray, NSSet这些类型,再由内部解析成字符串后赋给NSURLRequest。

转化过程大致是这样的:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
@{
     @"name": @"bang",
     @"phone": @{@"mobile":@"xx",@"home":@"xx"},
     @"families": @[@"father",@"mother"],
     @"nums": [NSSetsetWithObjects:@"1",@"2",nil]
}
->
@[
     field:@"name", value:@"bang",
     field:@"phone[mobile]", value:@"xx",
     field:@"phone[home]", value:@"xx",
     field:@"families[]", value:@"father",
     field:@"families[]", value:@"mother",
     field:@"nums", value:@"1",
     field:@"nums", value:@"2",
]
->
name=bang&phone[mobile]=xx&phone[home]=xx&families[]=father&families[]=mother&nums=1&num=2

第一部分是用户传进来的数据,支持包含NSArray,NSDictionary,NSSet这三种数据结构。
第二部分是转换成AFNetworking内自己的数据结构,每一个key-value对都用一个对象AFQueryStringPair表示,作用是最后可以根据不同的字符串编码生成各自的key=value字符串。主要函数是AFQueryStringPairsFromKeyAndValue,详见源码注释。
第三部分是最后生成NSURLRequest可用的字符串数据,并且对参数进行url编码,在AFQueryStringFromParametersWithEncoding这个函数里。

最后在把数据赋给NSURLRequest时根据不同的HTTP方法分别处理,对于GET/HEAD/DELETE方法,把参数加到URL后面,对于其他如POST/PUT方法,把数据加到body上,并设好HTTP头,告诉服务端字符串的编码。

B.HTTP Header

AFNetworking帮你组装好了一些HTTP请求头,包括语言Accept-Language,根据[NSLocale preferredLanguages]方法读取本地语言,告诉服务端自己能接受的语言。还有构建User-Agent,以及提供Basic Auth认证接口,帮你把用户名密码做base64编码后放入HTTP请求头。详见源码注释。

C.其他格式化方式

HTTP请求参数不一定是要key=value形式,可以是任何形式的数据,可以是json格式,苹果的plist格式,二进制protobuf格式等,AFNetworking提供了方法可以很容易扩展支持这些格式,默认就实现了json和plist格式。详见源码的类AFJSONRequestSerializer和AFPropertyListRequestSerializer。

2.构建multipart请求

构建Multipart请求是占篇幅很大的一个功能,AFURLRequestSerialization里2/3的代码都是在做这个事。

A.Multipart协议介绍

Multipart是HTTP协议为web表单新增的上传文件的协议,协议文档是rfc1867,它基于HTTP的POST方法,数据同样是放在body上,跟普通POST方法的区别是数据不是key=value形式,key=value形式难以表示文件实体,为此Multipart协议添加了分隔符,有自己的格式结构,大致如下:

—AaB03x
content-disposition: form-data; name=“name"

bang
–AaB03x
content-disposition: form-data; name=”pic”; filename=“content.txt”
Content-Type: text/plain

… contents of bang.txt …
–AaB03x–

以上表示数据name=bang以及一个文件,content.txt是文件名,… contents of bang.txt …是文件实体内容。分隔符—AaB03x是可以自定义的,写在HTTP头部里:

Content-type: multipart/form-data, boundary=AaB03x

每一个部分都有自己的头部,表明这部分的数据类型以及其他一些参数,例如文件名,普通字段的key。最后一个分隔符会多加两横,表示数据已经结束:—AaB03x—。

B.实现

接下来说说怎样构造Multipart里的数据,最简单的方式就是直接拼数据,要发送一个文件,就直接把文件所有内容读取出来,再按上述协议加上头部和分隔符,拼接好数据后扔给NSURLRequest的body就可以发送了,很简单。但这样做是不可用的,因为文件可能很大,这样拼数据把整个文件读进内存,很可能把内存撑爆了。

第二种方法是不把文件读出来,不在内存拼,而是新建一个临时文件,在这个文件上拼接数据,再把文件地址扔给NSURLRequest的bodyStream,这样上传的时候是分片读取这个文件,不会撑爆内存,但这样每次上传都需要新建个临时文件,对这个临时文件的管理也挺麻烦的。

第三种方法是构建自己的数据结构,只保存要上传的文件地址,边上传边拼数据,上传是分片的,拼数据也是分片的,拼到文件实体部分时直接从原来的文件分片读取。这方法没上述两种的问题,只是实现起来也没上述两种简单,AFNetworking就是实现这第三种方法,而且还更进一步,除了文件,还可以添加多个其他不同类型的数据,包括NSData,和InputStream。

AFNetworking里multipart请求的使用方式是这样:

01
02
03
04
05
06
07
08
09
10
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary*parameters = @{@"foo":@"bar"};
NSURL*filePath = [NSURLfileURLWithPath:@"file://path/to/image.png"];
[manager POST:@"http://example.com/resources.json"parameters:parameters constructingBodyWithBlock:^(idformData) {
    [formData appendPartWithFileURL:filePath name:@"image"error:nil];
} success:^(AFHTTPRequestOperation *operation,id responseObject) {
    NSLog(@"Success: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation,NSError *error) {
    NSLog(@"Error: %@", error);
}];

这里通过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

C.NSInputStream子类

NSURLRequest的setHTTPBodyStream接受的是一个NSInputStream*参数,那我们要自定义inputStream的话,创建一个NSInputStream的子类传给它是不是就可以了?实际上不行,这样做后用NSURLRequest发出请求会导致crash,提示[xx _scheduleInCFRunLoop:forMode:]: unrecognized selector。

这是因为NSURLRequest实际上接受的不是NSInputStream对象,而是CoreFoundation的CFReadStreamRef对象,因为CFReadStreamRef和NSInputStream是toll-free bridged,可以自由转换,但CFReadStreamRef会用到CFStreamScheduleWithRunLoop这个方法,当它调用到这个方法时,object-c的toll-free bridging机制会调用object-c对象NSInputStream的相应函数,这里就调用到了_scheduleInCFRunLoop:forMode:,若不实现这个方法就会crash。详见这篇文章。

3.源码注释


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 有妇之夫爱上我,怎么办 全身起疙瘩很痒怎么办 怀孕了肚子很痛怎么办 月经来肚子疼的厉害怎么办 孕妇9个月肚子疼怎么办 斗鱼身上长白点怎么办 8个月宝宝肚子疼怎么办 1岁半宝宝肚子痛怎么办 3岁宝宝肚子疼怎么办啊 吃的太辣肚子疼怎么办 2岁半宝宝肚子疼怎么办 2岁宝宝肚子痛哭怎么办 怀孕3个月拉肚子怎么办 一岁宝宝消化不良拉肚子怎么办 后背长好多痘痘怎么办 身上起疹子很痒怎么办 背上长好多痘痘怎么办 月经两三个月不来怎么办 来月经很少是褐色的怎么办 例假一天就没了怎么办 月经来一天就没了怎么办 假体隆胸8年怀孕怎么办 1岁宝宝长湿疹怎么办 2017卓达破产后怎么办 8岁儿童反复发烧怎么办 工商年报报错了怎么办 买房契税票丢了怎么办 合同地址写错了怎么办 货物被海关扣了怎么办 货被海关扣了怎么办 发票领用薄丢了怎么办 开票税率开错了怎么办 开错税率过月了怎么办 公司开不下去了怎么办 想注册一个公司怎么办手续 税率是3%开成5%怎么办 分列后0变没了怎么办 excel中求和得0怎么办 京东账号被黑了怎么办 合同上写错金额怎么办 裤子穿久了发亮怎么办