AFNetWorking(3.0)源码分析(四)——AFHTTPSessionManager(2)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013378438/article/details/83818867

在上一篇博客中,我们分析了AFHTTPSessionManager,以及它是如何实现GET/HEAD/PATCH/DELETE相关接口的。
我们还剩下POST相关接口没有分析,在这篇博客里面,我们就来分析一下POST相关接口是如何实现的。

multipart/form-data请求

在继续理解POST接口之前,我们先来了解一下HTTP协议中和POST相关的multipart/form-data请求。

关于multipart/form-data请求的内容,大部分都来源于这篇博客:HTTP协议之multipart/form-data请求分析

我们知道,根据HTTP 1.1的协议规定,我们的请求类型可以是GET, HEAD, PATCH, POST, DELETE, OPTIONS, TRANCE。 那么,什么是multipart/form-data请求呢?

http协议大家都知道是规定了以ASCII码传输,建立在tcp、ip协议之上的应用层规范。http协议的格式我们在上一篇博客中已经提及,分为 请求行请求头请求体 三部分。并且在我们发送请求时,可以附加上相关的参数。对于GET/PUT等方法,参数都是按照"key=vaule"的格式附加到URL中的,其中keyvaule都是ASCII的字符串。而当我们调用POST方法,将这些"key=vaule"参数添加加到body中时,则需要在请求头中指明Content-Typeapplication/x-www-form-urlencoded ,并在body中写入这些参数,而不是附加在URL中。

到目前为止,我所说的参数类型都是key=value格式的,但如果我们想向服务器上传一个文件,那么这种key=value格式显然是不太合适的。

为了解决向服务器上传文件及其他信息的需求,人们对POST请求作出扩展:在POST请求中,支持Content-Type:multipart/form-data的请求。 为了区别与其他类型的POST请求,我们在这里可以先将这类POST请求称作:
multipart/form-data请求

multipart/form-data请求

  1. 请求行上与其他POST请求一致,需要写明POST方法,URL,协议类型。
  2. 但是在请求头中,需要加上下面的头信息:
Content-Type: multipart/form-data; boundary=${bound}  

首先,它声明了请求体 body内容是符合multipart/form-data格式 。之后,指明body将会用到的分隔符boundary=${bound}${bound} 是我们指定的分隔符,用来分隔body的内容。 这个分隔符是可以任意自定义的,但是为了区别与body中的内容,我们都会将其定义为一个比较复杂的字符串,如--------------------56423498738365

  1. 设置完请求头后,接下来就是设置multipart/form-data格式的请求体。请求体的内容是字符串形式,但是有格式要求:
--${bound}
Content-Disposition: form-data; name="Filename"
 
HTTP.pdf
--${bound}
Content-Disposition: form-data; name="file000"; filename="HTTP协议详解.pdf"
Content-Type: application/octet-stream
 
%PDF-1.5
file content
%%EOF
--${bound}
Content-Disposition: form-data; name="Upload"
 
Submit Query
--${bound}--

上面是一个典型的multipart/form-data格式的请求体。

其中${bound}为之前头信息中的分隔符,如果头信息中规定为123,那么这里也要为123。

这个请求体是多个部分组成的:每一个部分都是以--分隔符开始的,然后是该部分内容的描述信息Content-Disposition:,如果传送的内容是一个文件的话,那么还会包含文件名信息,以及文件内容的类型。上面的第二个小部分其实是一个文件体的结构然后一个回车,然后是描述信息的具体内容

最后会以--分隔符--结尾,表示请求体结束。

以上就是关于multipart/form-data请求的概要知识。我们需要重点记忆的是form-data的body格式,在下面的代码分析中,我们会了解到,AFHTTPRequestSerializer是如何组装multipart/form-data请求的body的。

AFHTTPSessionManager & POST

我们先来看一下AFHTTPSessionManager提供的关于POST的接口:

- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                    parameters:(nullable id)parameters
                       success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                       failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;


- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
                               progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;

- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
                                headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                               progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                    parameters:(nullable id)parameters
     constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
                       success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                       failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;

- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
              constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
                               progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;

- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
                                headers:(nullable NSDictionary <NSString *, NSString *> *)headers
              constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
                               progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

接口比较多,一共6个,但其中的4个AF已经声明为废弃了。剩下的2个,才是AF所提供的POST接口。其余4个最终都会调用到这2个POST接口之一:

- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
                                headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                               progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure

- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
                                headers:(nullable NSDictionary <NSString *, NSString *> *)headers
              constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
                               progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

这两个接口的区别在于是否存在constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block参数

constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block 这个block参数,我们可以理解为AF的一个block回调,当AF在组装POST的form-data的body时,会回调到这个block,用户可以通过设置符合AFMultipartFormData协议formData,将自己要上传的文件信息附加到formData中,AF会将文件data添加的request 的body中。 具体是怎么做的,我们稍后会看到。

这样就是说,上面两个POST接口,一个不需要上传文件,而另一个需要上传文件。是否需要上传文件的POST接口的实现是不一样的。

我们先来看一下不需要上传文件的POST接口:

- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
                                headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                               progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure
{
    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters headers:headers uploadProgress:uploadProgress downloadProgress:nil success:success failure:failure];
    
    [dataTask resume];
    
    return dataTask;
}

可以发现,它最终还是会调用AFHTTPSessionManagerdataTaskWithHTTPMethod方法。这和我们上一篇中介绍的GET/PUT等方法的实现是一样的。沿着我们上一篇中介绍的脉络,就可以理解其实现。需要注意的是,与GET等方法不同,最终POST的参数是写在body中的,其Content-Type: application/x-www-form-urlencoded 。这里就不再冗述。

我们重点来看一下上传文件版本的POST接口的实现:

- (NSURLSessionDataTask *)POST:(NSString *)URLString
                    parameters:(id)parameters
                       headers:(NSDictionary<NSString *,NSString *> *)headers
     constructingBodyWithBlock:(void (^)(id<AFMultipartFormData> _Nonnull))block
                      progress:(void (^)(NSProgress * _Nonnull))uploadProgress
                       success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
    NSError *serializationError = nil;
    // 1. 用 AFHTTPRequestSerializer组装 multipart-Form 的body及相关的header,同时返回request
    NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError];
    for (NSString *headerField in headers.keyEnumerator) {
        [request addValue:headers[headerField] forHTTPHeaderField:headerField];
    }
    if (serializationError) {
        if (failure) {
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
        }
        
        return nil;
    }
    
    // 2. 对于multi-part form, 调用父类的upload task 方法,返回upload task
    __block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:uploadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        if (error) {
            if (failure) {
                failure(task, error);
            }
        } else {
            if (success) {
                success(task, responseObject);
            }
        }
    }];
    
    [task resume];
    // 3. 返回task
    return task;
}

可以看到,POST接口与之前介绍过的GET/PUT等接口的实现类似,均是用三步来提供task:

  1. 调用AFHTTPRequestSerializer的相关接口来组装request
  2. 调用父类AFURLSessionManager的方法来返回task
  3. 调用task resume启动任务,并向外返回该task

与之前介绍的接口的不同之处在于2点,

  1. 对于AFHTTPRequestSerializer, POST请求调用的是multipartFormRequestWithMethod而不是之前的requestWithMethod方法。
  2. 调用的父类方法,不是返回的NSURLSessionDataTask,而是调用uploadTaskWithStreamedRequest 接口。从这里也可以看出,带有block回调的POST接口,是设计用来向服务器上传文件的。

multipartFormRequestWithMethod

让我们把目光移到AFHTTPSessionManager的HTTP请求组装器AFHTTPRequestSerializer中,来看一下它是怎么组装POST request的。

AFHTTPRequestSerializer会调用multipartFormRequestWithMethod来组装上传文件的POST请求:

- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
                                              URLString:(NSString *)URLString
                                             parameters:(NSDictionary *)parameters
                              constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
                                                  error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(method);
    NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]);  // 肯定不是GET 和 HEAD方法
    // 1. 先获取request。 由于POST方法的parameters要用Form格式放在 body中,所以这里的 parameters 参数填写nil
    NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];

    // 2. 生成AFStreamingMultipartFormData对象,用来存储将会添加到POST body中的parameters
    __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
    if (parameters) {
        for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
            NSData *data = nil;
            if ([pair.value isKindOfClass:[NSData class]]) {
                data = pair.value;
            } else if ([pair.value isEqual:[NSNull null]]) {
                data = [NSData data];
            } else {
                data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
            }

            if (data) {
                [formData appendPartWithFormData:data name:[pair.field description]];
            }
        }
    }

    // 如果有用户传入的 construct Body block, 则会调用。这里主要是向用户提供回调时机,让用户可以传入要添加到POST body的file data
    if (block) {
        block(formData);
    }

    // 3. 将formData 真正附加到request 的body中 并返回
    return [formData requestByFinalizingMultipartFormData];
}

multipartFormRequestWithMethod方法会分3个步骤来组装POST请求:

  1. requestWithMethod方法,来返回对应的POST request
  2. 生成AFStreamingMultipartFormData对象form data,来存储要添加到POST request中的body 数据。
  3. 调用form datarequestByFinalizingMultipartFormData方法,将form data附加到POST request中。

关于第1个步骤,我们在上一篇中已经分析过,不再多说。重点是第2,3步骤,POST的body data是如何生成的,body data又是如何附加到POST request中的。

Generate body data

AFHTTPRequestSerializer是利用AFStreamingMultipartFormData对象来生成body data的。从类的命名就可以看出,AFStreamingMultipartFormData是通过数据流来提供body data的(主要是要上传的file data)。

关于生成body data的代码摘抄出来如下:

  // 2. 生成AFStreamingMultipartFormData对象,用来存储将会添加到POST body中的parameters
    __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
    if (parameters) {
        for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
            NSData *data = nil;
            if ([pair.value isKindOfClass:[NSData class]]) {
                data = pair.value;
            } else if ([pair.value isEqual:[NSNull null]]) {
                data = [NSData data];
            } else {
                data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
            }

            if (data) {
                [formData appendPartWithFormData:data name:[pair.field description]];
            }
        }
    }

    // 如果有用户传入的 construct Body block, 则会调用。这里主要是向用户提供回调时机,让用户可以传入要添加到POST body的file data
    if (block) {
        block(formData);
    }

上面的内容可以分为两部分:
(1) 生成AFStreamingMultipartFormData对象,并传入parameters
(2)调用block回调,让AFStreamingMultipartFormData对象接受来自用户的文件data

我们先来看一下AFStreamingMultipartFormData对象是如何生成的:

__block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
@interface AFStreamingMultipartFormData ()
@property (readwrite, nonatomic, copy) NSMutableURLRequest *request;
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;
@property (readwrite, nonatomic, copy) NSString *boundary;
@property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream;
@end
- (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest
                    stringEncoding:(NSStringEncoding)encoding
{
    self = [super init];
    if (!self) {
        return nil;
    }

    self.request = urlRequest;
    self.stringEncoding = encoding;
    self.boundary = AFCreateMultipartFormBoundary();
    self.bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding];

    return self;
}

form data对象的初始化函数很简单,就是记录了从外界传入的参数:urlRequest和encoding 类型。同时,初始化了其成员AFMultipartBodyStream对象

其中,boundary成员是form 的分隔符,是由AFCreateMultipartFormBoundary()函数生成的一个随机字符串。

AFMultipartBodyStream* bodyStream,则用来记录POST的body data。关于它是如何记录的,我们稍后会做分析。

知道了AFStreamingMultipartFormData form data对象是如何生成的后,我们回过头来看一下参数是如何附加到form data中的:

if (parameters) {
        for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
            NSData *data = nil;
            if ([pair.value isKindOfClass:[NSData class]]) {
                data = pair.value;
            } else if ([pair.value isEqual:[NSNull null]]) {
                data = [NSData data];
            } else {
                data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
            }

            if (data) {
                [formData appendPartWithFormData:data name:[pair.field description]];
            }
        }
    }

这里用到了上一篇博客中提到的AFQueryStringPairsFromDictionary方法以及AFQueryStringPair类型。然后,AF会将AFQueryStringPair中存储的value转换为NSData类型。

将vaule转换为NSData类型后,调用form data的:

- (void)appendPartWithFormData:(NSData *)data
                          name:(NSString *)name

将data和其对应的key附加到form data中。
我们来看一下AFStreamingMultipartFormDataappendPartWithFormData:name:方法是如何实现的:

- (void)appendPartWithFormData:(NSData *)data
                          name:(NSString *)name
{
    NSParameterAssert(name);

    NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
    [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"];

    // 附加一个AFHTTPBodyPart
    [self appendPartWithHeaders:mutableHeaders body:data];
}

form 首先会生成一个表示该data节点头的字典mutableHeaders,然后存入如下内容:

key: @"Content-Disposition"  value:@"form-data; name=\"%@\"", name

然后,将header 和 data 组合起来,存储到AFHTTPBodyPart 中。

    // 附加一个AFHTTPBodyPart
    [self appendPartWithHeaders:mutableHeaders body:data];
- (void)appendPartWithHeaders:(NSDictionary *)headers
                         body:(NSData *)body
{
    NSParameterAssert(body);

    AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
    bodyPart.stringEncoding = self.stringEncoding;
    bodyPart.headers = headers;
    bodyPart.boundary = self.boundary;
    bodyPart.bodyContentLength = [body length];
    bodyPart.body = body;

    [self.bodyStream appendHTTPBodyPart:bodyPart];
}

传入的参数被form data转换为了对应的AFHTTPBodyPart,然后,AFHTTPBodyPart 会被AFMultipartBodyStream *bodyStream添加到其HTTPBodyPart中。

OK,到这里,我们已经涉及到了好几个类的关系。我们现在先暂停一下,总结一下上面AFHTTPSessionManager是如何为POST请求生成form data body的。上面涉及到的几个类的关系如下图:

在这里插入图片描述

通过上图,这几个类之间的关系会清楚许多。首先,我们要生成form data类型POST请求,需要调用AFHTTPRequestSerializer的相关方法,利用AFHTTPRequestSerializer来生成对应的request,这个和其他请求GET/PUT等是一样的。

而在AFHTTPRequestSerializer, 会生成一个AFStreamingMultipartFormData对象来存储所有POST 请求的body data

AFStreamingMultipartFormData的内部实现中,会针对每一个POST 请求参数,生成一个AFHTTPBodyPart对象,然后这些对象又会存储到其成员变量AFMultipartBodyStream对象中。

如果细心的话,可以注意到,用于存储参数的AFMultipartBodyStream类是继承自NSInputStream的,这也就暗示了,最终将这些参数附加到request中时,是通过流的方式进行的,这对于上传大的文件,很有帮助。

对于各个类的实现细节,我们暂不去管,首先从整体上把握类之间的关系。在稍后的部分中,我们将会进一步分析类实现的细节。

上面是关于parameter的存储方式,如果用户需要上传文件的话,AF会调用block回调来给用户上传文件的时机:

    //  如果有用户传入的 construct Body block, 则会调用。这里主要是向用户提供回调时机,让用户可以传入要添加到POST body的file data
    if (block) {
        block(formData);
    }

这里的block定义是:

(void (^)(id <AFMultipartFormData> formData))block

这里的block会传入一个符合AFMultipartFormData协议的对象, 这里传入的是AFStreamingMultipartFormData对象。

这里有个小思考,为什么block的参数是一个协议类型,而不是具体的AFStreamingMultipartFormData类型? 其实我们之间将block定义改写为:

(void (^)(AFStreamingMultipartFormData *formData))block

在逻辑上也是完全行得通的。但是,这会对外部对象过多的暴露AF的实现细节,或者说和用户需求功能不相干的细节,也暴露给了用户,这样就对代码的误用留下了隐患,同时,对于用户的使用也造成了不必要的麻烦。

AF在这里的处理是向外暴露一个AFMultipartFormData协议,该协议只有和用户上传文件相关的接口,而屏蔽了AFStreamingMultipartFormData中如boundaryrequest等无关的属性。

这就是通过协议向外提供了一个窄接口,屏蔽了无关的实现。这也是我们可以借鉴的一个面向对象编程的技巧。

我们来看一下AFMultipartFormData协议都定义了那些接口:

@protocol AFMultipartFormData
// 将file data附加到form data中
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                         name:(NSString *)name
                        error:(NSError * _Nullable __autoreleasing *)error;

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

- (void)appendPartWithInputStream:(nullable NSInputStream *)inputStream
                             name:(NSString *)name
                         fileName:(NSString *)fileName
                           length:(int64_t)length
                         mimeType:(NSString *)mimeType;

- (void)appendPartWithFileData:(NSData *)data
                          name:(NSString *)name
                      fileName:(NSString *)fileName
                      mimeType:(NSString *)mimeType;

- (void)appendPartWithFormData:(NSData *)data
                          name:(NSString *)name;

// 将headers信息添加到form data中,并跟一个body data
- (void)appendPartWithHeaders:(nullable NSDictionary <NSString *, NSString *> *)headers
                         body:(NSData *)body;

// 考虑到3G带宽的限制,文件流可能会报错:"request body stream exhausted"。因此AF提供了一个可以设置包大小和延迟时间的接口。
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
                                  delay:(NSTimeInterval)delay;

@end

可以看到,AFMultipartFormData协议主要是提供了三个功能:
(1)用户上传文件data
(2)添加form 的headers
(3)设置form data的流 包大小和延迟时间。

我们先来看用户上传data相关的接口在AFStreamingMultipartFormData中是如何实现的:

那其中一个接口做例子:

- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                         name:(NSString *)name
                     fileName:(NSString *)fileName
                     mimeType:(NSString *)mimeType
                        error:(NSError * __autoreleasing *)error
{
    NSParameterAssert(fileURL);
    NSParameterAssert(name);
    NSParameterAssert(fileName);
    NSParameterAssert(mimeType);

    // 检测文件的相关属性,如果有错误,直接返回NO及error
    if (![fileURL isFileURL]) {
        NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"Expected URL to be a file URL", @"AFNetworking", nil)};
        if (error) {
            *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo];
        }

        return NO;
    } else if ([fileURL checkResourceIsReachableAndReturnError:error] == NO) {
        NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"File URL not reachable.", @"AFNetworking", nil)};
        if (error) {
            *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo];
        }

        return NO;
    }

    NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:error];
    if (!fileAttributes) {
        return NO;
    }

    // 组装form data form data中关于file的节点的头信息
    NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
    [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
    [mutableHeaders setValue:mimeType forKey:@"Content-Type"];

    // 将头信息以及file data的相关信息存储为AFHTTPBodyPart.
    AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
    bodyPart.stringEncoding = self.stringEncoding;
    bodyPart.headers = mutableHeaders;
    bodyPart.boundary = self.boundary;
    bodyPart.body = fileURL; // 注意AFHTTPBodyPart的body属性,是id类型,可以直接存储file URL,file data, 或NSInputStream
    bodyPart.bodyContentLength = [fileAttributes[NSFileSize] unsignedLongLongValue];
    [self.bodyStream appendHTTPBodyPart:bodyPart];

    return YES;
}

其余的接口都大同小异,读者可以自行分析。

我们再来看一下AFStreamingMultipartFormData中又如何设置包的大小和延迟时间,这里只是简单的记录下来:

- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
                                  delay:(NSTimeInterval)delay
{
    self.bodyStream.numberOfBytesInPacket = numberOfBytes;
    self.bodyStream.delay = delay;
}

Append body data to POST Request

通过上面的分析,我们知道,form data是如何存储传入POST body的headers和file data信息的。这其中涉及到三个类: AFStreamingMultipartFormDataAFMultipartBodyStreamAFHTTPBodyPart

对于multi form data中的每一个节(被分隔符分割),都是对应一个AFHTTPBodyPart,而所有的这些节,都被AFStreamingMultipartFormData统一append 到AFMultipartBodyStream中。

到目前为止,AFStreamingMultipartFormDataPOST request还没有发生实质性关系,AFStreamingMultipartFormData中仅是存储了相关body data,但还未将这些body data附加到request中。

当调用AFStreamingMultipartFormDatarequestByFinalizingMultipartFormData时,会设置POST request,并将其中存储的POST form data信息附加到request 上:

return [formData requestByFinalizingMultipartFormData];
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
    if ([self.bodyStream isEmpty]) {
        return self.request;
    }

    // Reset the initial and final boundaries to ensure correct Content-Length
    [self.bodyStream setInitialAndFinalBoundaries];
    // 将request的body stream设置为self.bodyStream。使得POST request的body和self.bodyStream建立关联(AFMultipartBodyStream)
    [self.request setHTTPBodyStream:self.bodyStream];
    
    // 设置request 的请求头为:Content-Type:multipart/form-data; boundary=self.boundary, 表明request body是multi-part form类型
    [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"];
    [self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"];

    return self.request;
}

上面的逻辑比较好理解,重点是设置request的bodyStream:

   [self.request setHTTPBodyStream:self.bodyStream];

当把self.bodyStream(AFMultipartBodyStream)设置为request的body stream后,当request请求被发送时,会自动调用read方法:

- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length
- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length
{
    // 如果当前的stream 没有打开,则直接返回0
    if ([self streamStatus] == NSStreamStatusClosed) {
        return 0;
    }

    NSInteger totalNumberOfBytesRead = 0;

    while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) { // 读取iOS stream指定的大小 或 用户设置的最小包大小 (以min为准)
        if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
            if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) { // update currentHTTPBodyPart
                break;
            }
        } else {
            NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead; // 本次while循环可以读取的buffer 大小: 本次read:maxLength允许读取的最大字节数 - 已经读取的字节数
            NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength]; //读取数据到buffer中,并返回读取到的字节数
            if (numberOfBytesRead == -1) { // 读取字节数等于-1 表示读取失败,退出循环
                self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
                break;
            } else {
                totalNumberOfBytesRead += numberOfBytesRead; // 更新总的字节数

                if (self.delay > 0.0f) { // 根据用户设置的延迟时间 ,休息一下
                    [NSThread sleepForTimeInterval:self.delay];
                }
            }
        }
    }

    return totalNumberOfBytesRead; // 返回读取的总数据
}

AFMultipartBodyStreamread:maxLength方法中,会依次遍历其存储的AFHTTPBodyPart,并调用AFHTTPBodyPartread:maxLength方法:

// AFHTTPBodyPart
- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length
{
    // 读取buffer, 并更新phase
    NSInteger totalNumberOfBytesRead = 0;

    // AFHTTPBodyPart 读取stream 时,会分为三个/四个阶段 对应了multipart form data的结构
    
    // phase 1. 开头的分隔符
    if (_phase == AFEncapsulationBoundaryPhase) {
        NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding];
        totalNumberOfBytesRead += [self readData:encapsulationBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
    }

    // phase 2. form 节点的header信息
    if (_phase == AFHeaderPhase) {
        NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding];
        totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
    }

    // phase 3. form body
    if (_phase == AFBodyPhase) {
        NSInteger numberOfBytesRead = 0;

        numberOfBytesRead = [self.inputStream read:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
        if (numberOfBytesRead == -1) {
            return -1;
        } else {
            totalNumberOfBytesRead += numberOfBytesRead;

            if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) {
                [self transitionToNextPhase];
            }
        }
    }

    // phase 4. form 的结束符(如果是最后一个 form 节点才会有这个阶段)
    if (_phase == AFFinalBoundaryPhase) {
        NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]);
        totalNumberOfBytesRead += [self readData:closingBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
    }

    return totalNumberOfBytesRead;
}

因为iOS每次input stream 读取的字节流大小并不能预知,因此,AFHTTPBodyPart会用变量_phase 来记录body data 已经读取到了哪一个部分。当下次input stream再来读取数据时,会按照当前的_phase 来读取AFHTTPBodyPart的不同内容。

对于AFHTTPBodyPart的内容读取,会调用AFHTTPBodyPart

- (NSInteger)readData:(NSData *)data intoBuffer:(uint8_t *)buffer maxLength:(NSUInteger)length

方法:

- (NSInteger)readData:(NSData *)data
           intoBuffer:(uint8_t *)buffer
            maxLength:(NSUInteger)length
{
    NSRange range = NSMakeRange((NSUInteger)_phaseReadOffset, MIN([data length] - ((NSUInteger)_phaseReadOffset), length));
    [data getBytes:buffer range:range];

    _phaseReadOffset += range.length;

    if (((NSUInteger)_phaseReadOffset) >= [data length]) {
        [self transitionToNextPhase];
    }

    return (NSInteger)range.length;
}

readData方法中,会记录在当前阶段已经读取数据的偏移值_phaseReadOffset。在下次读取时,会从偏移值的地方开始,读取data剩余的数据。如果_phaseReadOffset >= [data length],则说明当前阶段的data已经读取完毕,调用transitionToNextPhase转入到下一个阶段。

当然,对于AFHTTPBodyPartAFBodyPhase阶段所对应的inputStream属性data,因为本身就是流,因此不用记录_phaseReadOffset,而直接调用NSInputStreamread:maxLength方法即可。在AFBodyPhase阶段,需要手动判断流状态,来转入到下一个阶段:

if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) {
                [self transitionToNextPhase];
}

然我们在看一下,AFHTTPBodyPart是如何转入到下一个阶段的:

- (BOOL)transitionToNextPhase {
    if (![[NSThread currentThread] isMainThread]) {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self transitionToNextPhase];  // transitionToNextPhase 必须在主线程调用
        });
        return YES;
    }

    // 根据当前阶段,转换到下一个阶段
    switch (_phase) {
        case AFEncapsulationBoundaryPhase:
            _phase = AFHeaderPhase;
            break;
        case AFHeaderPhase:
            [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
            [self.inputStream open];
            _phase = AFBodyPhase;
            break;
        case AFBodyPhase:
            [self.inputStream close];
            _phase = AFFinalBoundaryPhase;
            break;
        case AFFinalBoundaryPhase:
        default:
            _phase = AFEncapsulationBoundaryPhase; // 默认或初始化为  AFEncapsulationBoundaryPhase(包装分隔符) 阶段
            break;
    }
    _phaseReadOffset = 0;

    return YES;
}

在transitionToNextPhase方法中,转换阶段要做的事情多数时间很简单:

  1. 修改当前的_phase等于下一个阶段 _phase =nextPhase
  2. 清空当前阶段已经读取的偏移量_phaseReadOffset = 0;

这里需要注意的是两点,第一,transitionToNextPhase会保证在main线程调用:

 if (![[NSThread currentThread] isMainThread]) {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self transitionToNextPhase];  // transitionToNextPhase 必须在主线程调用
        });
        return YES;
    }

第二,对于AFBodyPhase,会将input stream附加到main 线程的runloop中,并打开流:

 case AFHeaderPhase:
            [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
            [self.inputStream open];
            _phase = AFBodyPhase;
            break;

AFBodyPhase 阶段结束,转入AFFinalBoundaryPhase 阶段前,需要将流关闭:

      case AFBodyPhase:
            [self.inputStream close];
            _phase = AFFinalBoundaryPhase;
            break;

上面就是POST 请求的multipart form data的组装过程。我们要留意的是NSInputStream的使用方式,即必须附加到runloop上:

[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

这样做可以避免在没有数据可读时阻塞代理对象的操作。

总结

在这一篇博客中,我们了解了AFHTTPSessionManager中关于POST接口的实现。同时,我们也了解了HTTP协议中,multipart form data的body格式。

至此,对于AFHTTPSessionManager的分析也就告一段落。同时,我们也了解了AFHTTPRequestSerializer的大部分接口。

接下来,我们将会对AFHTTPRequestSerializer剩下的接口进行分析,同时会了解reponse的解析类AFHTTPResponseSerializerAFJSONResponseSerializer

猜你喜欢

转载自blog.csdn.net/u013378438/article/details/83818867