AFNetworking3.1源码解读<二>

上篇文章AFNetworking3.1源码解读<一>主要学习了Session相关部分,本篇主要学习Serialization中的AFURLRequestSerialization部分。AFURLRequestSerialization顾名思义是请求序列化,也就是创建出符合HTTP协议的GET、POST、HEAD、PUT、DELETE等请求。在AFNetworking中RequstSerialization主要实现了普通请求和multipart/form-data请求的序列化。

普通请求序列化

HTTP Method 是GET、HEAD、DELETE

如果HTTP Method 是GET、HEAD、DELETE则请求会被序列化成Query String,比如在bing里面搜索helloworld后请求的URL为

https://www.bing.com/search?q=helloworld&go=Submit&qs=n&form=QBLH&pq=helloworld&sc=8-10&sp=-1&sk=&cvid=C1DAD98D70EA4C0CB1B38F3E04016570

其中q=helloworld&go=Submit&qs=n&form=QBLH&pq=helloworld&sc=8-10&sp=-1&sk=&cvid=C1DAD98D70EA4C0CB1B38F3E04016570就是查询字符串,参数间用&分割,参数如果是嵌套Dictionary怎会变成[key][key1]=[value1]&[key][key2]=[value2],如果是嵌套的是Array怎会变成key[]=value1&key[]=value2。在AFNetworking中Query string请求序列化一定得是NSDctionary类型。

其他

其他主要指的是POST请求,POST请求序列化时分为AFURLRequestSerialization、AFJSONRequestSerializer、AFPropertyListRequestSerializer,前者是后两者的父类,这三者的区别为,当请求序列化是AFURLRequestSerialization时,Content-Type=application/x-www-form-urlencoded,body存放的是QueryString;当请求序列化是AFJSONRequestSerializer时,Content-Type=application/json,body存放的是输入参数的JSON格式;当请求是AFPropertyListRequestSerializer时,Content-Type=application/x-plist,body是plist格式的数据。

multipart/form-data请求序列化

首先来看下mulitpart/form-data协议格式:

HTTP Header部分:
需要指定ContentType: multipart/form-data; boundary=Sample-Boundary.

HTTP Body部分:

–Sample-Boundary(换行)

Content-Disposition: form-data; name=”xxx”; filename=”xxx.xx”(换行)

Content-Type :MIME类型(换行)

(再换行)

helloworld(此处正文部分,helloworld表示要传输的内容,换行)

(再换行)

–Sample-Boundary(开始另一部分,内容格式与前面部分一致)

Content-Disposition: form-data; name=”xxx”; filename=”xxx.xx”(换行)

Content-Type :MIME类型(换行)

(再换行)

helloworld(此处正文部分,helloworld表示要传输的内容,换行)

–Sample-Boundary–(结尾)
上述就是Multipart的协议格式,大体来说就是分为3个部分,分别为boundary(–Sample-Boundary,–Sample-Boundary–),header(Content-Disposition: form-data; name=”xxx”; filename=”xxx.xx”,Content-Type :MIME类型),内容(helloworld)。下面就讲下AFNetworking是如果实现上述3部分协议格式的。

Boundary

因为协议中有开始Boundary、中间Boundary、结束Boundary,所以AFNetworking中定义了3个全局的boundary函数(其中boundary是随机生成的):AFMultipartFormInitialBoundary、AFMultipartFormEncapsulationBoundary、AFMultipartFormFinalBoundary
,例如AFMultipartFormInitialBoundary的实现为:

static inline NSString * AFMultipartFormInitialBoundary(NSString *boundary) {
    return [NSString stringWithFormat:@"--%@%@", boundary, kAFMultipartFormCRLF];
}

这里的header主要是指Content-Disposition: form-data; name=”xxx”; filename=”xxx.xx”和,Content-Type :MIME类型,AFNetworking在实现multipart/form-data传输时定义了一个叫做AFMultipartFormData的protocol,使用者需要实现该protocol,该protocol的接口类似

- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                         name:(NSString *)name
                        error:(NSError * _Nullable __autoreleasing *)error;

从上面的接口就可以确定header了,filename=[fileURL lastPathComponent],name使用者提供,MIME类型是通过fileURL的扩展名计算出来的,具体计算代码如下:

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;
    }
}

另外AFMultipartFormData protocol也提供了由使用者指定filname、name、MIME type等的接口,具体科参考AFMultipartFormData。

content(内容)

AFNetworking主要是通过NSInputStream来管理输入内容的,具体做法是定义了两个类,一个AFMultipartBodyStream继承自NSInputStream,负责直接对接NSURLRequest的HTTPBodyStream;另一个类是AFHTTPBodyPart,主要负责multipart中具体的某一部分,整体类结构如下图所示:
multipartStruct

这里需要注意的是:AFMultipartBodyStream继承自NSInputStream,而NSInputStream是一个abstract类,所以需要自己实现NSInputStream的所有方法;而AFMultipartFormData protocol中每一个接口相当于产生了一个AFHTTPBodyPart,负责该部分的boundary、header、body,这三部分构成了一个大的输入,而这个大的输入是通过NSInputStream输入到AFMultipartBodyStream继而输入到NSURLRequest的HTTPBodyStream中,相关代码如下:

- (NSInputStream *)inputStream {
    if (!_inputStream) {
        if ([self.body isKindOfClass:[NSData class]]) {
            _inputStream = [NSInputStream inputStreamWithData:self.body];
        } else if ([self.body isKindOfClass:[NSURL class]]) {
            _inputStream = [NSInputStream inputStreamWithURL:self.body];
        } else if ([self.body isKindOfClass:[NSInputStream class]]) {
            _inputStream = self.body;
        } else {
            _inputStream = [NSInputStream inputStreamWithData:[NSData data]];
        }
    }

    return _inputStream;
}

本篇完~