[TOC]
Pre
- AFNetworking分析版本:4.0.1
调试所用代码(AFNetworking demo iOS)
XCTestExpectation *expectation = [self expectationWithDescription:@"Request should succeed"];
[self.sessionManager.requestSerializer setValue:@"default value"
forHTTPHeaderField:@"field"];
[self.sessionManager
POST:@"post"
parameters:@{@"key":@"value"}
headers:@{@"field":@"value"}
constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
[formData appendPartWithFileData:[@"Data" dataUsingEncoding:NSUTF8StringEncoding]
name:@"DataName"
fileName:@"DataFileName"
mimeType:@"data"];
}
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
XCTAssertTrue([task.originalRequest.allHTTPHeaderFields[@"field"] isEqualToString:@"value"]);
XCTAssertTrue([responseObject[@"files"][@"DataName"] isEqualToString:@"Data"]);
XCTAssertTrue([responseObject[@"form"][@"key"] isEqualToString:@"value"]);
[expectation fulfill];
}
failure:nil];
[self waitForExpectationsWithCommonTimeout];
AFMultipartFormData
协议
在AFHTTPSessionManager.h
中,我们可以看到这样的方法
- (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;
这里的(nullable void (^)(id <AFMultipartFormData> formData))block
是一个接受单个参数并向HTTP的body添加data的block。该block遵循AFMultipartFormData
协议,在block中设置需要上传的文件。
多表单参数的请求封装(上述POST方法的实现流程)
封装NSMutableURLRequest
对象
NSError *serializationError = nil;
NSMutableURLRequest *request =
[self.requestSerializer multipartFormRequestWithMethod:@"POST"
URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString]
parameters:parameters
constructingBodyWithBlock:block
error:&serializationError];
- 基本判断。方法不能为空且不能为
GET
和HEAD
NSParameterAssert(method);
NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]);
NSMutableURLRequest *mutableRequest = [self requestWithMethod:method
URLString:URLString
parameters:nil error:error];
- 用request创建一个
AFStreamingMultipartFormData
数据对象
__block AFStreamingMultipartFormData *formData =
[[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest
stringEncoding:NSUTF8StringEncoding];
看一下AFStreamingMultipartFormData
的整体结构。 初始化方法内部:
self.request = urlRequest; //对request进行持有
self.stringEncoding = encoding; //NSUTF8StringEncoding
self.boundary = AFCreateMultipartFormBoundary();
self.bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding];
3.1 这里的%08X
为十六进制,即生成两个十六进制随机数拼接在字符串后面,比如"Boundary+919640865615927E"
static NSString * AFCreateMultipartFormBoundary() {
return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
}
3.2 AFMultipartBodyStream
对象初始化
AFMultipartBodyStream
所含属性- 初始化方法
self.stringEncoding = encoding;
self.HTTPBodyParts = [NSMutableArray array];
self.numberOfBytesInPacket = NSIntegerMax;
- 参数处理(若parameter不为空)
4.1 先通过AFQueryStringPairsFromDictionary()函数对parameters进行处理,返回的是数组(此处流程见【AFNetworking 分析】NSURLSessionManager & NSHTTPSessionManager 初始化及请求封装)
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
···
}
- 用文首调试代码运行到这里可以看到,返回了一个数组,数组中包含一个
AFQueryStringPair
对象 - 对象内容为: 4.2 遍历返回的
AFQueryStringPair
数组,对数据进行处理封装为NSData
对象,并根据data和name构建Request的header和body
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];
}
4.3 根据data为request设置headers和body
if (data) {
[formData appendPartWithFormData:data name:[pair.field description]];
}
}
- (void)appendPartWithFormData:(NSData *)data
name:(NSString *)name
{
NSParameterAssert(name);
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"];
[self appendPartWithHeaders:mutableHeaders body:data];
}
走到这里看一下mutableHeaders
好的继续走到appendPartWithHeaders: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];
也就是说,将封装好的bodyPart
添加至AFStreamingMultipartFormData
-> AFMultipartBodyStream *bodyStream
属性 -> 可变数组属性HTTPBodyParts
中。 5. block处理formData(其实还是往formData中添加数据)
if (block) {
block(formData);
}
- 对多表单数据的再处理,比如设置一下
MultipartRequest
的bodyStream
或者其特有的content-type
等等
return [formData requestByFinalizingMultipartFormData];
6.1 若本AFStreamingMultipartFormData
对象的bodyStream
为空,则直接返回自己持有的request
对象
if ([self.bodyStream isEmpty]) {
return self.request;
}
6.2 重置初始和最终的boundaries
以确保正确的Content-length
[self.bodyStream setInitialAndFinalBoundaries];
- 在该方法中,首先判断本AFMultipartBodyStream *bodyStream对象的HTTPBodyParts数组中是否有元素,若有
// a. 逐个设置每个AFHTTPBodyPart元素的hasInitialBoundary和hasFinalBoundary为NO
for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) {
bodyPart.hasInitialBoundary = NO;
bodyPart.hasFinalBoundary = NO;
}
// b. 将本bodyStream的首个元素的hasInitialBoundary设置为YES,hasFinalBoundary设置为NO
[[self.HTTPBodyParts firstObject] setHasInitialBoundary:YES];
[[self.HTTPBodyParts lastObject] setHasFinalBoundary:YES];
}
}
6.3 为request设置bodyStream
- 那么,设置了bodyStream后什么时候对其进行处理?
[self.request setHTTPBodyStream:self.bodyStream];
我们跳转进入HTTPBodyStream
理解一下官方注释:
HTTPBodyStream
将request的body设置为指定流的内容。
所提供的流应不开启;request将接管流的delegate。整个流的内容将作为请求的HTTP body进行传输。
注意,主体流和主体数据(由setHTTPBody:设置)是互斥的。
—设置一个会清除另一个。
- 查看文件结构也可以发现
说明可能后面会用到这里的方法,先放一放。
6.4 为request设置Content-type
和Content-length
[self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"];
6.5 为request设置Content-length
[self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"];
- 计算bodyStream的contentLength,即是计算bodyStream中的HTTPBodyParts数组中每个AFHTTPBodyPart元素的contentLength之和(以下为计算AFHTTPBodyPart元素的contentLength代码)
unsigned long long length = 0;
NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding];
length += [encapsulationBoundaryData length];
NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding];
length += [headersData length];
length += _bodyContentLength;
NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]);
length += [closingBoundaryData length];
至今为止,bodyStream封装完毕,多表单必备的headers也已经写进了request
为请求添加headers
for (NSString *headerField in headers.keyEnumerator) {
[request setValue:headers[headerField] forHTTPHeaderField:headerField];
}
请求封装序列化错误处理
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
return nil;
}
- 值得一提的是,这里的
completionQueue
属性来自于上层NSURLSessionManager
,暴露在头文件中,若未指定的话则操作一般会放在主队列上。我们可以通过给它赋其他队列的方式(异步等)来使我们想要进行的操作异步执行(上传埋点等等),避免阻塞主线程。
NSURLSessionDataTask封装
__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);
}
}
}];
- 调用
NSURLSession
的uploadTaskWithStreamedRequest:
方法将request封装为NSURLSessionUploadTask
对象
NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithStreamedRequest:request];
- 为上传任务添加代理(与
addDelegateForDataTask:
方法处理类似,详情见【AFNetworking 分析】NSURLSessionManager & NSHTTPSessionManager 初始化及请求封装)
[self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];
执行任务
[task resume];
task生成后,就会去建立连接,request读取、编码等 bodyStream一定是连接服务器成功之后才会处理!
来源:oschina
链接:https://my.oschina.net/u/4135139/blog/4948402