AFNetworking 發送 GET、POST 等請求時能夠直接將參數按照字典結構傳入,最終編碼到 url 中或者是 body 實體中,同時也支持按照 multipart/form-data 格式,將多種不一樣的數據合入到 body 中進行發送,而這些就涉及到 AFNetworking 的請求序列化類,也就是 AFURLRequestSerialization。javascript
AFURLRequestSerialization 是一個協議,它定義了一個方法用於序列化參數到 NSURLRequest 中,AFHTTPRequestSerializer 實現了這個協議,並實現了相應的方法。它不只提供了普通的參數編碼方法,也提供了 form-data 格式的 request 構建方法,也就是下面的方法html
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
error:(NSError *__autoreleasing *)error
複製代碼
首先簡單介紹一下 form-data,multipart/form-data 主要用於 POST方法中傳遞多種格式和含義的數據,在 body 中引入 boundary 的概念,用分割線將多部分數據融合到一個 body 中發送給服務端。那麼對於一個簡單的 form-data,它發送的 body 內容可能以下java
--Boundary+FD2E180F039993ED
Content-Disposition: form-data; name="myArray[]"
v1
--Boundary+FD2E180F039993ED
Content-Disposition: form-data; name="myArray[]"
v2
--Boundary+FD2E180F039993ED
Content-Disposition: form-data; name="myArray[]"
v3
--Boundary+FD2E180F039993ED
Content-Disposition: form-data; name="mydic[key1]"
value1
--Boundary+FD2E180F039993ED
Content-Disposition: form-data; name="mydic[key2]"
value2
--Boundary+FD2E180F039993ED
header: headerkey
BodyData
--Boundary+FD2E180F039993ED--
複製代碼
它的特色是json
在 AFNetworking 中,要發送 form-data,能夠經過以下方式發送數組
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer.timeoutInterval = 100;
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/plain", @"text/html",@"application/json", @"text/json" ,@"text/javascript", nil];;
[manager POST:@"https://www.baidu.com" parameters:@{@"mydic":@{@"key1":@"value1",@"key2":@"value2"},
@"myArray":@[@"v1", @"v2", @"v3"]
} headers:nil 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) {
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
複製代碼
主要用到 AFHTTPSessionManager 定義的以下方法bash
- (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;
複製代碼
它的內部實現,主要作了這幾件事app
從中能夠看出,請求序列化主要發生在 multipartFormRequestWithMethod 方法中,而 AFHttpSessionManager 默認的 requestSerializer 是 AFHTTPAFHTTPRequestSerializer。oop
AFHTTPAFHTTPRequestSerializer 對於 form-data 提供了以下方法進行序列化ui
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
error:(NSError *__autoreleasing *)error
複製代碼
在方法實現裏主要作了如下事情編碼
appendPartWithFormData: name:
方法添加到 AFStreamingMultipartFormData 中requestByFinalizingMultipartFormData
方法構建 request那麼 AFStreamingMultipartFormData 是一個什麼類呢。
AFNetworking 定義的 AFStreamingMultipartFormData 類用於表徵一個 form-data 格式 body 的數據,它遵循 AFMultipartFormData 協議,能管理 boundary 字符串、用於向 request 傳輸數據的 NSInputStream 對象。
其中對於 form-data 的每個 part,AFNetworking 定義了一個 AFHTTPBodyPart 類,其中包含以下信息
AFStreamingMultipartFormData 所包含的 NSInputStream 類,實質上是繼承自 NSInputStream 的子類 AFMultipartBodyStream,AFMultipartBodyStream 有一個 HTTPBodyParts 屬性,是一個 AFHTTPBodyPart 類型的數組,全部 append 到 AFStreamingMultipartFormData 的 part,最後都轉化爲一個 AFHTTPBodyPart 對象加入到了 AFMultipartBodyStream 的 HTTPBodyParts 中。
具體來講,AFMultipartFormData 協議(也就是 AFStreamingMultipartFormData 類)定義了以下一些 append 方法
appendPartWithFileURL: name: error:
添加文件路徑內的文件內容到 form-dataappendPartWithFileURL: name: fileName: mimeType: error:
添加文件路徑內的文件內容到 form-data,指定文件名和 mimeTypeappendPartWithInputStream: name: fileName: length: mimeType:
添加 inputStream 到 form-dataappendPartWithFileData: name: fileName: mimeType:
添加 NSData 到 form-dataappendPartWithFormData: name:
添加 NSData 到 form-dataappendPartWithHeaders: body:
添加自定義 header 和 body 到 form-data下面以 appendPartWithFormData 爲例看下具體實現
- (void)appendPartWithFormData:(NSData *)data
name:(NSString *)name
{
NSParameterAssert(name);
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
// 每一塊數據,默認帶上 Content-Disposition 做爲頭部
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"];
[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;
// 複用一個 boundary
bodyPart.boundary = self.boundary;
// body 長度
bodyPart.bodyContentLength = [body length];
bodyPart.body = body;
// 添加到 stream 中
[self.bodyStream appendHTTPBodyPart:bodyPart];
}
複製代碼
能夠看到,就是根據數據構造一個 AFHTTPBodyPart 對象添加到 bodyStream 屬性中;至於文件和 inputStream,則是直接將文件 url 和 inputStream 對象賦值給 id 類型的 body。
這樣將全部數據都 append 到了 AFStreamingMultipartFormData 中之後,再調用 AFStreamingMultipartFormData 的 requestByFinalizingMultipartFormData 方法就能夠構造一個 NSMutableURLRequest 對象了,而在 requestByFinalizingMultipartFormData 方法中,主要作了以下工做
[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"];
複製代碼
AFMultipartBodyStream 直接繼承自 NSInputStream,它維護一個 包含所有 AFHTTPBodyPart 的數組,當經過 request 發起一個 NSURLSessionUploadTask 之後,因爲設置了 request 的 HTTPBodyStream,則系統會嘗試從 AFMultipartBodyStream 讀取 body 數據,這裏就涉及到了 AFMultipartBodyStream 的 read: maxLength:
方法,它從流中讀取數據到 buffer 中,並返回實際讀取的數據長度(該長度最大爲 len)。而實際上 AFMultipartBodyStream 的 numberOfBytesInPacket 屬性就能夠限制讀取數據的最大長度。
{
if ([self streamStatus] == NSStreamStatusClosed) {
// 流已關閉,返回長度 0
return 0;
}
NSInteger totalNumberOfBytesRead = 0;
// 一直從 HTTPBodyParts 讀取到字節數達到 length 爲止
while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
// 若是還未開始讀取,或者當前 part 已經讀取結束,則進入下一個
if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
break;
}
} else {
NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
// 從 part 中讀取數據
NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
if (numberOfBytesRead == -1) {
// 讀取出錯
self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
break;
} else {
// 更新總讀取字節數
totalNumberOfBytesRead += numberOfBytesRead;
if (self.delay > 0.0f) {
[NSThread sleepForTimeInterval:self.delay];
}
}
}
}
return totalNumberOfBytesRead;
}
複製代碼
這裏經過一個 currentHTTPBodyPart 對象對 AFMultipartBodyStream 維護的 AFHTTPBodyPart 數組進行遍歷,讀取其中每個 AFHTTPBodyPart 對象的數據到 buffer 中。AFHTTPBodyPart 類也實現了同名的 read 方法,在這個方法裏,按照以下順序,讀取相應部分的數據
例如讀取頂部邊界數據以下
NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding];
totalNumberOfBytesRead += [self readData:encapsulationBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
複製代碼
可是當讀取到 body 部分時要注意,因爲 body 是一個 id 類型,外界主要設置的可能值有 NSData、NSURL、NSInputStream 等,AFNetworking 在這裏統一將 body 的讀取歸一化爲 inputStream 流方式讀取,按照以下規則構建 inputStream
- (NSInputStream *)inputStream {
// inputStream 根據 body 的類別返回不一樣的數據源
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;
}
複製代碼
讀取到 body 部分時則啓動 stream,讀取完 body 之後關閉 stream
// 這裏是根據當前 phase 切換到下一端 phase 的邏輯
case AFHeaderPhase:
// header -> body
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.inputStream open];
_phase = AFBodyPhase;
break;
case AFBodyPhase:
// body -> 底部邊界
[self.inputStream close];
_phase = AFFinalBoundaryPhase;
break;
複製代碼
以上就是 AFNetworking 對於 form-data 請求的完整處理,基於 inputStream,將多種不一樣類型的 form-data 用統一的代碼模型處理,對外暴露的方法簡潔一致,於是便於使用和理解。