AFURLRequestSerialization
模塊主要做的兩樣事情:
1.創(chuàng)建普通NSMutableURLRequest
請求對象
2.創(chuàng)建multipart NSMutableURLRequest
請求對象
此外還有比如:
處理查詢的 URL 參數(shù)
AFURLRequestSerialization
是一個(gè)協(xié)議,它定義了一個(gè)方法:
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error
AFHTTPRequestSerializer
及其子類遵循這個(gè)協(xié)議。
現(xiàn)在從AFHTTPRequestSerializer這個(gè)類入手分析。
1.創(chuàng)建普通NSMutableURLRequest請求
//創(chuàng)建一般的NSMutableURLRequest對象,設(shè)置HTTPMethod、請求屬性、HTTPHeader和處理參數(shù)
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(method);
NSParameterAssert(URLString);
NSURL *url = [NSURL URLWithString:URLString];
NSParameterAssert(url);
//創(chuàng)建URLRequest、設(shè)置請求的方法
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
//通過mutableObservedChangedKeyPaths設(shè)置NSMutableURLRequest請求屬性
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
//用KVC的方式,給request設(shè)置屬性值
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
//設(shè)置http header和參數(shù)(拼接到url還是放到http body中)
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
這個(gè)方法做了三件事:
1.設(shè)置request請求類型mutableRequest.HTTPMethod = method;
2.設(shè)置request的一些屬性。
2.1這里用到了AFHTTPRequestSerializerObservedKeyPaths()
c函數(shù)。
//單例。觀察者keyPath集合。需要觀察的request屬性:allowsCellularAccess、cachePolicy、HTTPShouldHandleCookies、HTTPShouldUsePipelining、networkServiceType、timeoutInterval
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
});
return _AFHTTPRequestSerializerObservedKeyPaths;
}
這個(gè)函數(shù)創(chuàng)建了一個(gè)數(shù)組單例,里面裝的都是NSURLRequest
的屬性。
2.2mutableObservedChangedKeyPaths
是AFHTTPRequestSerializer類的一個(gè)屬性,它在-init
方法中進(jìn)行了初始化。另外在-init
方法中還對上面設(shè)置的6個(gè)與NSURLRequest
相關(guān)的屬性添加觀察者(KVO):
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
//為請求的屬性添加觀察者
/*
observer: 觀察者對象. 其必須實(shí)現(xiàn)方法observeValueForKeyPath:ofObject:change:context:.
keyPath: 被觀察的屬性,其不能為nil.
options: 設(shè)定通知觀察者時(shí)傳遞的屬性值,新值、舊值,通常設(shè)置為NSKeyValueObservingOptionNew。
context: 一些其他的需要傳遞給觀察者的上下文信息,通常設(shè)置為nil
*/
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
KVO觸發(fā)的方法,mutableObservedChangedKeyPaths
用于記錄這些屬性的變化(由我們自己設(shè)置request的屬性值):
//觀察者接收通知,通過實(shí)現(xiàn)下面的方法,完成對屬性改變的響應(yīng)。將新的屬性存儲在一個(gè)名為 mutableObservedChangedKeyPaths的集合中
//change: 屬性值,根據(jù)- addObserver: forKeyPath: options: context:的Options設(shè)置,給出對應(yīng)的屬性值
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(__unused id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == AFHTTPRequestSerializerObserverContext) {
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
} else {
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}
這些被監(jiān)聽的屬性值改變時(shí)是這樣通知他們的觀察者對象的:
/*
willChangeValueForKey通知觀察到的對象,給定屬性的值即將更改。在手動(dòng)實(shí)現(xiàn)KVO時(shí),使用此方法通知觀察對象,鍵值即將更改。
值更改后,必須使用相同的參數(shù)調(diào)用相應(yīng)的didChangeValueForKey:
*/
- (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
[self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
_allowsCellularAccess = allowsCellularAccess;
[self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
}
2.3最后用KVC給request設(shè)置這些屬性值。
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
3.對網(wǎng)絡(luò)請求參數(shù)進(jìn)行編碼
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
//設(shè)置請求頭 不會(huì)覆蓋原有的header
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
NSString *query = nil;//格式化的請求參數(shù)
if (parameters) {
//如果有自定義block
if (self.queryStringSerialization) {
NSError *serializationError;
//用自定義block來格式化請求參數(shù)
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
} else {
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
//調(diào)用 AFQueryStringFromParameters 將參數(shù)轉(zhuǎn)換為查詢參數(shù)
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
//將參數(shù) parameters 添加到 URL 或者 HTTP body 中
//GET HEAD DELETE,參數(shù)拼接到url
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];//根據(jù)是否已有查詢字符串進(jìn)行拼接?已有就用‘&’,沒有就用‘?’
}
}
//參數(shù)添加到httpbody中 ,比如POST PUT
else {
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
3.1設(shè)置請求頭。從self.HTTPRequestHeaders
中拿到header,賦值到請求的request中去,如果原先的header已經(jīng)存在就不進(jìn)行設(shè)置。
3.2對網(wǎng)絡(luò)請求參數(shù)進(jìn)行編碼。
如果有自定的block來格式化(轉(zhuǎn)碼)請求參數(shù)就用自定義block。
if (self.queryStringSerialization) {
NSError *serializationError;
//用自定義block來格式化請求參數(shù)
query = self.queryStringSerialization(request, parameters, &serializationError);
如果沒有自定義block來處理就使用AF的轉(zhuǎn)碼方式:
//把dictionary參數(shù)轉(zhuǎn)換、拼接成字符串參數(shù)
/*
NSDictionary *info = @{@"account":@"zhangsan",@"password":@"123456"};
AFQueryStringFromParameters(info)的結(jié)果是:account=zhangsan&password=123456 (沒有百分比編碼)
NSDictionary *info = @{@"student":@{@"name":@"zhangsan",@"age":@"15"}};
AFQueryStringFromParameters(info)的結(jié)果是:student[name]=zhangsan&student[age]=15 (沒有百分比編碼)
*/
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
//拆分?jǐn)?shù)組返回的參數(shù)字符串
return [mutablePairs componentsJoinedByString:@"&"];
}
//網(wǎng)絡(luò)請求參數(shù)拼接處理入口。
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
//遞歸處理value。如果當(dāng)前的 value 是一個(gè)集合類型的話,那么它就會(huì)不斷地遞歸調(diào)用自己。
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
//排序。根據(jù)需要排序的對象的description來進(jìn)行升序排列,
//description返回的是NSString,compare:使用的是NSString的compare:方法
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
// Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}
主要是根據(jù)value的類型來用AFQueryStringPairsFromKeyAndValue
這個(gè)函數(shù)遞歸處理value參數(shù),直到解析的類型不是array\dictionary\set。
- 這里涉及到一個(gè)類
AFQueryStringPair
:
//參數(shù)轉(zhuǎn)化的中間模型
@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;
- (instancetype)initWithField:(id)field value:(id)value;
- (NSString *)URLEncodedStringValue;
@end
@implementation AFQueryStringPair
- (instancetype)initWithField:(id)field value:(id)value {
self = [super init];
if (!self) {
return nil;
}
self.field = field;
self.value = value;
return self;
}
//百分號編碼后,用"="拼接field value值
- (NSString *)URLEncodedStringValue {
if (!self.value || [self.value isEqual:[NSNull null]]) {
return AFPercentEscapedStringFromString([self.field description]);
} else {
return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
}
}
@end
AFQueryStringPair
這個(gè)類相當(dāng)于是一個(gè)參數(shù)轉(zhuǎn)化的中間模型,在AFQueryStringPairsFromKeyAndValue
函數(shù)遞歸處理的最后:
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
就是這樣把一對field-value值保存起來。再通過-URLEncodedStringValue
方法對field-value百分比編碼、"="拼接。
舉個(gè)例子理解一下這個(gè)參數(shù)格式化:
NSDictionary *info = @{@"account":@"zhangsan",@"password":@"123456"};
AFQueryStringFromParameters(info)的結(jié)果是:account=zhangsan&password=123456 (沒有百分比編碼)
NSDictionary *info = @{@"student":@{@"name":@"zhangsan",@"age":@"15"}};
AFQueryStringFromParameters(info)的結(jié)果是:student[name]=zhangsan&student[age]=15 (沒有百分比編碼)
- 關(guān)于參數(shù)百分比編碼:
根據(jù)RFC 3986的規(guī)定:URL百分比編碼的保留字段分為:
1.':' '#' '[' ']' '@' '?' '/'
2.'!' '$' '&' ''' '(' ')' '*' '+' ',' ';' '='
在對查詢字段百分比編碼時(shí),'?'和'/'可以不用編碼,其他的都要進(jìn)行編碼。下面這段代碼結(jié)合注釋也很好理解,就不過多展開了。
//對字符串進(jìn)行百分比編碼
NSString * AFPercentEscapedStringFromString(NSString *string) {
//過濾需要編碼的字符
static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
//?和/不需要被編碼,所以除了?和/之外的字符要從URLQueryAllowedCharacterSet中剔除
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
// 為了處理類似emoji這樣的字符串,rangeOfComposedCharacterSequencesForRange 使用了while循環(huán)來處理,也就是把字符串按照batchSize分割處理完再拼回。
static NSUInteger const batchSize = 50;
NSUInteger index = 0;
NSMutableString *escaped = @"".mutableCopy;
while (index < string.length) {
NSUInteger length = MIN(string.length - index, batchSize);
NSRange range = NSMakeRange(index, length);
// To avoid breaking up character sequences such as ????????
//對emoji這類特殊字符的處理。分開一個(gè)字符串時(shí)保證我們不會(huì)分開被稱為代理對的東西。
range = [string rangeOfComposedCharacterSequencesForRange:range];
NSString *substring = [string substringWithRange:range];
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];//編碼
[escaped appendString:encoded];
index += range.length;
}
return escaped;
}
3.3根據(jù)請求類型,將參數(shù)字符串添加到 URL 或者 HTTP body 中
如果是GET、HEAD、DELETE,則把請求參數(shù)拼接到url后面的。而POST、PUT是把請求參數(shù)拼接到http body。
2.創(chuàng)建multipart NSMutableURLRequest請求對象
這一部分主要是對上傳文件做的一些封裝。Multipart是HTTP協(xié)議為Web表單新增的上傳文件的協(xié)議,Content-Type的類型擴(kuò)充了multipart/form-data用以支持向服務(wù)器發(fā)送二進(jìn)制數(shù)據(jù)。它基于HTTP POST的方法,數(shù)據(jù)同樣是放在body,跟普通POST方法的區(qū)別是數(shù)據(jù)不是key=value形式。更多關(guān)于multipart/form-data請求請戳:HTTP協(xié)議之multipart/form-data請求分析
請求體HTTP Body的格式大致如下:
--boundary //上邊界 //“boundary”是一個(gè)邊界,沒有實(shí)際的意義,可以用任意字符串來替代
Content-Disposition: form-data; name=xxx; filename=xxx
Content-Type: application/octet-stream
(空一行)
文件內(nèi)容的二進(jìn)制數(shù)據(jù)
--boundary-- //下邊界
請求體內(nèi)容分為四個(gè)部分:
1.上邊界
2.頭部,告訴服務(wù)器要做數(shù)據(jù)上傳,包含:
a. 服務(wù)器的接收字段name=xxx。xxx是負(fù)責(zé)上傳文件腳本中的 字段名,開發(fā)的時(shí)候,可以咨詢后端程序員,不需要自己設(shè)定。
b. 文件在服務(wù)器中保存的名稱filename=xxx。xxx可以自己指定,不一定和本地原本的文件名相同
c. 上傳文件的數(shù)據(jù)類型 application/octet-stream
3.上傳文件的數(shù)據(jù)部分(二進(jìn)制數(shù)據(jù))
4.下邊界部分,嚴(yán)格按照字符串格式來設(shè)置.
上邊界部分和下邊界部分的字符串,最后都要轉(zhuǎn)換成二進(jìn)制數(shù)據(jù),和文件部分的二進(jìn)制數(shù)據(jù)拼接在一起,作為請求體發(fā)送給服務(wù)器.
NSURLConnection筆記-上傳文件
要構(gòu)造Multipart里的數(shù)據(jù)有三種方式:
最簡單的方式就是直接拼數(shù)據(jù),要發(fā)送一個(gè)文件,就直接把文件所有內(nèi)容讀取出來,再按上述協(xié)議加上頭部和分隔符,拼接好數(shù)據(jù)后扔給NSURLRequest的body就可以發(fā)送了,很簡單。但這樣做是不可用的,因?yàn)槲募赡芎艽?,這樣拼數(shù)據(jù)把整個(gè)文件讀進(jìn)內(nèi)存,很可能把內(nèi)存撐爆了。
第二種方法是不把文件讀出來,不在內(nèi)存拼,而是新建一個(gè)臨時(shí)文件,在這個(gè)文件上拼接數(shù)據(jù),再把文件地址扔給NSURLRequest的bodyStream,這樣上傳的時(shí)候是分片讀取這個(gè)文件,不會(huì)撐爆內(nèi)存,但這樣每次上傳都需要新建個(gè)臨時(shí)文件,對這個(gè)臨時(shí)文件的管理也挺麻煩的。
第三種方法是構(gòu)建自己的數(shù)據(jù)結(jié)構(gòu),只保存要上傳的文件地址,邊上傳邊拼數(shù)據(jù),上傳是分片的,拼數(shù)據(jù)也是分片的,拼到文件實(shí)體部分時(shí)直接從原來的文件分片讀取。這方法沒上述兩種的問題,只是實(shí)現(xiàn)起來也沒上述兩種簡單,AFNetworking就是實(shí)現(xiàn)這第三種方法,而且還更進(jìn)一步,除了文件,還可以添加多個(gè)其他不同類型的數(shù)據(jù),包括NSData,和InputStream。
在Multipart這一部分代碼比較長,涉及到幾個(gè)類和協(xié)議,這里先把它們的關(guān)系圖放出來:
2.1AFHTTPBodyPart
AFHTTPBodyPart
實(shí)際上做的是對Multipart請求體各部分(初始邊界、頭部、內(nèi)容數(shù)據(jù)實(shí)體、結(jié)束邊界)做拼接和讀取的封裝。
NSData \ FileUrl \ NSInputStream 類型的數(shù)據(jù)在AFHTTPBodyPart
中都轉(zhuǎn)換成NSInputStream。
//根據(jù)body的數(shù)據(jù)類型,NSData\NSURL\NSInputStream轉(zhuǎn)換成輸入流并返回
//inputStream值保存了數(shù)據(jù)實(shí)體,沒有分隔符和頭部
- (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;
}
_inputStream只保存了數(shù)據(jù)實(shí)體(body),不包含上下邊界和頭部信息。
AFHTTPBodyPart
讀取數(shù)據(jù)是邊讀邊拼接的,用一個(gè)狀態(tài)機(jī)來確定現(xiàn)在數(shù)據(jù)讀到哪一部分,依次往后傳遞進(jìn)行狀態(tài)切換。要注意的是,在讀取數(shù)據(jù)實(shí)體(body)部分是用流(NSInputStream)來處理的,讀之前打開流,讀完之后關(guān)閉流然后進(jìn)入下一階段:
//用狀態(tài)機(jī)切換
- (BOOL)transitionToNextPhase {
//主線程執(zhí)行本方法
if (![[NSThread currentThread] isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^{
[self transitionToNextPhase];
});
return YES;
}
switch (_phase) {
//讀取完初始邊界
case AFEncapsulationBoundaryPhase:
_phase = AFHeaderPhase;
break;
//讀取完頭部,準(zhǔn)備讀取body,打開流 準(zhǔn)備接受數(shù)據(jù)
case AFHeaderPhase:
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.inputStream open];
_phase = AFBodyPhase;
break;
//讀取完body,關(guān)閉流
case AFBodyPhase:
[self.inputStream close];
_phase = AFFinalBoundaryPhase;
break;
//讀取完結(jié)束邊界
case AFFinalBoundaryPhase:
default:
_phase = AFEncapsulationBoundaryPhase;
break;
}
//重置
_phaseReadOffset = 0;
return YES;
}
結(jié)合狀態(tài)機(jī),讀取數(shù)據(jù)是分塊進(jìn)行的,拼接數(shù)據(jù)也是分塊的,邊讀邊拼接。并且使用totalNumberOfBytesRead
的局部變量來保存已經(jīng)讀取的字節(jié)數(shù),以此來定位要讀的數(shù)據(jù)位置:
//把請求體讀到buffer中。邊讀取邊拼接數(shù)據(jù)
- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length
{
NSInteger totalNumberOfBytesRead = 0;
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)];
}
if (_phase == AFHeaderPhase) {
NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding];
totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
}
if (_phase == AFBodyPhase) {
NSInteger numberOfBytesRead = 0;
//讀取給定緩沖區(qū)中給定的字節(jié)數(shù)。返回的結(jié)果:正數(shù)表示讀取的字節(jié)數(shù)。0表示達(dá)到緩沖區(qū)的結(jié)尾。-1表示操作失敗;
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];
}
}
}
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;
}
- (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;//記錄當(dāng)前階段已被讀取的字節(jié)數(shù)
if (((NSUInteger)_phaseReadOffset) >= [data length]) {
[self transitionToNextPhase];
}
return (NSInteger)range.length;
}
通過閱讀上面這兩個(gè)方法,很容易猜測,- read: maxLength:
這個(gè)方法會(huì)在其他的代碼中的某個(gè)循環(huán)中被調(diào)用(主要是數(shù)據(jù)實(shí)體部分的讀取拼接是分塊進(jìn)行而不是一次性的)。
2.2AFMultipartBodyStream
AFMultipartBodyStream
繼承NSInputStream ,遵循NSStreamDelegate協(xié)議。
AFMultipartBodyStream
封裝了整個(gè)multipart數(shù)據(jù)的讀取。它有一個(gè)NSSArray類型的HTTPBodyParts
屬性,用來保存每一個(gè)AFHTTPBodyPart
對象,所以很直觀地就想到了是對多文件上傳的封裝。
對整個(gè)multipart數(shù)據(jù)的讀取,主要是根據(jù)讀取的位置確定當(dāng)前讀的是哪個(gè)AFHTTPBodyPart
,然后調(diào)用AFHTTPBodyPart
的- read: maxLength:
讀取、拼接數(shù)據(jù),最后記錄讀取的每一個(gè)AFHTTPBodyPart
的數(shù)據(jù)長度總和。AFMultipartBodyStream
重寫了NSInputStream的- read: maxLength:
方法:
//重寫方法
- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length
{
if ([self streamStatus] == NSStreamStatusClosed) {
return 0;
}
NSInteger totalNumberOfBytesRead = 0;
//self.numberOfBytesInPacket用于3G網(wǎng)絡(luò)請求優(yōu)化,指定每次讀取的數(shù)據(jù)包大小,建議值kAFUploadStream3GSuggestedPacketSize
//遍歷讀取數(shù)據(jù)
while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
//self.currentHTTPBodyPart不存在,或者沒有可讀的字節(jié)(已經(jīng)讀完)
if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
//看看還有沒有下一個(gè)。把下一個(gè)請求體賦值給當(dāng)前請求體,如果下一個(gè)是nil就退出循環(huán)
if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
break;
}
} else {
//剩余數(shù)據(jù)長度?
//這里maxLength是進(jìn)入AFHTTPBodyPart讀取的maxLength
NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
//讀到buffer中
NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
if (numberOfBytesRead == -1) {
self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
break;
} else {
totalNumberOfBytesRead += numberOfBytesRead;
//延時(shí)用于3G網(wǎng)絡(luò)請求優(yōu)化,讀取數(shù)據(jù)延時(shí),建議值kAFUploadStream3GSuggestedDelay
if (self.delay > 0.0f) {
[NSThread sleepForTimeInterval:self.delay];
}
}
}
}
return totalNumberOfBytesRead;
}
對初始邊界和結(jié)束邊界進(jìn)行設(shè)置,比如多文件上傳時(shí)設(shè)置第一個(gè)文件的初始邊界,和最后一個(gè)文件的結(jié)束邊界。
除此之外,它還對多文件上傳的初始邊界和結(jié)束邊界進(jìn)行設(shè)置。
對于多文件上傳的請求體格式:(以多文件+普通文本為例)
多文件+普通文本 上傳的請求體格式如下:
--boundary\r\n // 第一個(gè)文件參數(shù)//上邊界,不過也可以寫成這樣:\r\n--boundary\r\n
Content-Disposition: form-data; name=xxx; filename=xxx\r\n
Content-Type:image/jpeg\r\n\r\n
(空一行)
上傳文件的二進(jìn)制數(shù)據(jù)部分
\r\n--boundary\r\n // 第二個(gè)文件參數(shù)//上邊界 //文件一的下邊界可略,在這句之前插入文件一的下邊界\r\n--boundary--也可以
Content-Disposition: form-data; name=xxx; filename=xxx\r\n
Content-Type:text/plain\r\n\r\n
(空一行)
上傳文件的二進(jìn)制數(shù)據(jù)部分
\r\n--boundary\r\n //普通文本參數(shù) //上邊界
Content-Disposition: form-data; name="xxx"\r\n\r\n //name是服務(wù)器的接收字段,不需要自己制定
(空一行)
普通文本二進(jìn)制數(shù)據(jù)
\r\n--boundary-- // 下邊界
在兩個(gè)文件之間不需要把上一個(gè)文件的結(jié)束邊界也拼接上去,\r\n--boundary\r\n
暫且叫做“中間邊界”吧。知道這一協(xié)議格式之后,那么下面這段代碼也很好理解了:
//初始邊界和結(jié)束邊界的設(shè)置。多文件上傳時(shí)設(shè)置第一個(gè)文件的上邊界,和最后一個(gè)文件的下邊界
- (void)setInitialAndFinalBoundaries {
if ([self.HTTPBodyParts count] > 0) {
for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) {
bodyPart.hasInitialBoundary = NO;
bodyPart.hasFinalBoundary = NO;
}
[[self.HTTPBodyParts firstObject] setHasInitialBoundary:YES];
[[self.HTTPBodyParts lastObject] setHasFinalBoundary:YES];
}
}
由于AFMultipartBodyStream
繼承NSInputStream
,遵循NSStreamDelegate
協(xié)議,所以這個(gè)類里還重寫了很多NSStream
的方法:
#pragma mark - NSInputStream
//重寫方法
- (BOOL)getBuffer:(__unused uint8_t **)buffer
length:(__unused NSUInteger *)len
{
return NO;
}
//判斷數(shù)據(jù)是否已經(jīng)讀完了,open狀態(tài)就是還有數(shù)據(jù)
- (BOOL)hasBytesAvailable {
return [self streamStatus] == NSStreamStatusOpen;
}
#pragma mark - NSStream
- (void)open {
if (self.streamStatus == NSStreamStatusOpen) {
return;
}
self.streamStatus = NSStreamStatusOpen;
[self setInitialAndFinalBoundaries];
self.HTTPBodyPartEnumerator = [self.HTTPBodyParts objectEnumerator];
}
- (void)close {
self.streamStatus = NSStreamStatusClosed;
}
- (id)propertyForKey:(__unused NSString *)key {
return nil;
}
- (BOOL)setProperty:(__unused id)property
forKey:(__unused NSString *)key
{
return NO;
}
//設(shè)置runloop為了讓NSStreamDelegate收到stream狀態(tài)改變回調(diào)。不過這里NSURLRequest沒有用到delegate處理狀態(tài)改變就寫成空實(shí)現(xiàn)了。
- (void)scheduleInRunLoop:(__unused NSRunLoop *)aRunLoop
forMode:(__unused NSString *)mode
{}
- (void)removeFromRunLoop:(__unused NSRunLoop *)aRunLoop
forMode:(__unused NSString *)mode
{}
2.3AFStreamingMultipartFormData
AFStreamingMultipartFormData
遵循AFMultipartFormData
協(xié)議。是對AFMultipartBodyStream
更上一層的封裝。
AFStreamingMultipartFormData
管理了一個(gè)AFMultipartBodyStream
類型的屬性bodyStream
。調(diào)用AFStreamingMultipartFormData
對象的幾種append方法就可以添加 FileURL/NSData/NSInputStream幾種不同類型的數(shù)據(jù),AFStreamingMultipartFormData
內(nèi)部把這些數(shù)據(jù)轉(zhuǎn)換成一個(gè)個(gè)AFHTTPBodyPart
,并添加到AFMultipartBodyStream
里(用AFMultipartBodyStream
的HTTPBodyParts數(shù)組把它們一個(gè)個(gè)保存起來)。最后把AFMultipartBodyStream
賦給原來NSMutableURLRequest
的bodyStream:
//通過本地文件url獲取數(shù)據(jù)
- (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);
//url不是fileurl
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;
}
//路徑不可達(dá)
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;
}
//設(shè)置 http請求體的header
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
[mutableHeaders setValue:mimeType forKey:@"Content-Type"];
//生成AFHTTPBodyPart對象,拼接到AFMultipartBodyStream對象數(shù)組中
AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
bodyPart.stringEncoding = self.stringEncoding;
bodyPart.headers = mutableHeaders;
bodyPart.boundary = self.boundary;
bodyPart.body = fileURL;
bodyPart.bodyContentLength = [fileAttributes[NSFileSize] unsignedLongLongValue];//獲取文件大小
[self.bodyStream appendHTTPBodyPart:bodyPart];
return YES;
}
//把數(shù)據(jù)跟請求建立聯(lián)系的核心方法
//數(shù)據(jù)最終通過setHTTPBodyStream:傳遞給request
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
if ([self.bodyStream isEmpty]) {
return self.request;
}
// Reset the initial and final boundaries to ensure correct Content-Length
[self.bodyStream setInitialAndFinalBoundaries];
//將輸入流作為請求體
[self.request setHTTPBodyStream:self.bodyStream];
//設(shè)置請求頭
[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;
}
NSURLSession發(fā)送請求時(shí)會(huì)讀取這個(gè)bodyStream
,在讀取數(shù)據(jù)是會(huì)調(diào)用bodyStream的- read: maxLength:
方法,也即AFMultipartBodyStream
重寫的- read: maxLength:
方法,不斷讀取之前append的AFHTTPBodyPart數(shù)據(jù)直到讀完。
2.4創(chuàng)建multipart NSMutableURLRequest請求對象
//multipart傳數(shù)據(jù)
//GET和HEAD不能用multipart傳數(shù)據(jù),一般都是用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"]);
NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];
__block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
if (parameters) {
//把請求參數(shù)也放在multipart里
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]];
}
}
}
//執(zhí)行對外暴露的block接口。
//比如可以在block里拼接其他一些文件數(shù)據(jù)。調(diào)用AFStreamingMultipartFormData的幾個(gè)append方法
if (block) {
block(formData);
}
//把stream跟request建立聯(lián)系的核心方法
//數(shù)據(jù)最終通過setHTTPBodyStream:傳遞給request
return [formData requestByFinalizingMultipartFormData];
}
2.5其他
在AFMultipartBodyStream
中有以下這么幾個(gè)方法看得不太懂,不知道為什么要這樣寫:
#pragma mark - Undocumented CFReadStream Bridged Methods
- (void)_scheduleInCFRunLoop:(__unused CFRunLoopRef)aRunLoop
forMode:(__unused CFStringRef)aMode
{}
- (void)_unscheduleFromCFRunLoop:(__unused CFRunLoopRef)aRunLoop
forMode:(__unused CFStringRef)aMode
{}
- (BOOL)_setCFClientFlags:(__unused CFOptionFlags)inFlags
callback:(__unused CFReadStreamClientCallBack)inCallback
context:(__unused CFStreamClientContext *)inContext {
return NO;
}
NSURLRequest的setHTTPBodyStream接受的是一個(gè)NSInputStream*參數(shù),那我們要自定義inputStream的話,創(chuàng)建一個(gè)NSInputStream的子類傳給它是不是就可以了?實(shí)際上不行,這樣做后用NSURLRequest發(fā)出請求會(huì)導(dǎo)致crash,提示[xx _scheduleInCFRunLoop:forMode:]: unrecognized selector。
這是因?yàn)镹SURLRequest實(shí)際上接受的不是NSInputStream對象,而是CoreFoundation的CFReadStreamRef對象,因?yàn)镃FReadStreamRef和NSInputStream是toll-free bridged,可以自由轉(zhuǎn)換,但CFReadStreamRef會(huì)用到CFStreamScheduleWithRunLoop這個(gè)方法,當(dāng)它調(diào)用到這個(gè)方法時(shí),object-c的toll-free bridging機(jī)制會(huì)調(diào)用object-c對象NSInputStream的相應(yīng)函數(shù),這里就調(diào)用到了_scheduleInCFRunLoop:forMode:,若不實(shí)現(xiàn)這個(gè)方法就會(huì)crash。
3.其他
AFJSONRequestSerializer和AFPropertyListRequestSerializer這兩個(gè)AFHTTPRequestSerializer的子類的實(shí)現(xiàn)都比較簡單,主要是對這個(gè)協(xié)議方法進(jìn)行重寫。具體代碼閱讀都沒什么難度,就不展開講了。
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
詳細(xì)源碼注釋請戳:https://github.com/huixinHu/AFNetworking-
參考文章:
AFNetworking到底做了什么
AFNetworking2.0源碼解析<二>
http://www.cnblogs.com/chenxianming/p/5674652.html
通讀AFN②--AFN的上傳和下載功能分析、SessionTask及相應(yīng)的session代理方法的使用細(xì)節(jié)