AFURLRequestSerialization功能是在創(chuàng)建網(wǎng)絡(luò)請(qǐng)求的時(shí)候幫我們創(chuàng)建一個(gè)NSURLRequest检激,并封裝請(qǐng)求參數(shù)到NSURLRequest中。
讓我們一起學(xué)習(xí)一下創(chuàng)建一個(gè)好的NSURLRequest都需要做些什么吧竹捉。
學(xué)習(xí)AFURLRequestSerialization之前,我們要準(zhǔn)備好兩個(gè)知識(shí)點(diǎn):multipart/form-data請(qǐng)求和url編碼。
multipart/form-data請(qǐng)求是這篇內(nèi)容的重點(diǎn)榜聂,需要我們明白multipart/form-data請(qǐng)求和一般的請(qǐng)求的不同點(diǎn)在哪里焰情,準(zhǔn)備一下這方面的知識(shí)陌凳,我前面一篇文章已經(jīng)講過了,需要的可以看一下内舟。
Url編碼這里簡(jiǎn)單介紹一下合敦,夠我們用就行了。URI是統(tǒng)一資源標(biāo)識(shí)的意思验游,通常我們所說的Url只是URI的一種充岛。下面提到的Url編碼保檐,實(shí)際上應(yīng)該指是URI編碼。
為什么需要Url編碼
因?yàn)閁rl中有些字符會(huì)引起歧義崔梗。例如Url參數(shù)字符串中使用key=value鍵值對(duì)這樣的形式來(lái)傳參展东,鍵值對(duì)之間以&符號(hào)分隔,如http://localhost:8080/a?q=abc&ie=utf-8炒俱,如果你的value字符串中包含了?盐肃、=或者&,那么勢(shì)必會(huì)造成接收Url的服務(wù)器解析錯(cuò)誤权悟,因此必須將引起歧義的&和= 符號(hào)進(jìn)行轉(zhuǎn)義砸王,也就是對(duì)其進(jìn)行編碼。
哪些會(huì)引起歧義
RFC3986文檔規(guī)定峦阁,Url中只允許包含英文字母(a-zA-Z)谦铃、數(shù)字(0-9)、-_.~4個(gè)特殊字符以及所有保留字符榔昔。
RFC3986文檔還規(guī)定了保留字符和不安全字符驹闰。
保留字符:
通用定界符:":", "#", "[", "]", "@", "?", "/";
子分隔符: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=";
不安全字符:
" ", """, "<", ">", "#", "%", "{", "}", "|", "", "^", "[", "]", "`", "~";
因此在你的url如果包含不安全字符,那么就需要進(jìn)行編碼撒会,而如果你的參數(shù)中包含上述這些字符(保留字符和不安全字符)都需要進(jìn)行編碼嘹朗。但是在In RFC 3986 - Section 3.4中規(guī)定"?"、"/"可以不需要編碼诵肛。
怎么編碼
! | * | " | ' | ( | ) | ; | : | @ | & |
---|---|---|---|---|---|---|---|---|---|
%21 | %2A | %22 | %27 | %28 | %29 | %3B | %3A | %40 | %26 |
= | + | $ | , | / | ? | % | # | [ | ] |
---|---|---|---|---|---|---|---|---|---|
%3D | %2B | %24 | %2C | %2F | %3F | %25 | %23 | %5B | %5D |
對(duì)于非ASCII字符屹培,需要使用ASCII字符集的超集進(jìn)行編碼得到相應(yīng)的字節(jié),然后對(duì)每個(gè)字節(jié)執(zhí)行百分號(hào)編碼怔檩。 對(duì)于Unicode字符褪秀,RFC文檔建議使用utf-8對(duì)其進(jìn)行編碼得到相應(yīng)的字節(jié),然后對(duì)每個(gè)字節(jié)執(zhí)行百分號(hào)編碼薛训。如“中文”使用UTF-8字符集得到 的字節(jié)為0xE4 0xB8 0xAD 0xE6 0x96 0x87媒吗,經(jīng)過Url編碼之后得到“%E4%B8%AD%E6%96%87”。
如果某個(gè)字節(jié)對(duì)應(yīng)著ASCII字符集中的某個(gè)非保留字符乙埃,則此字節(jié)無(wú)需使用百分號(hào)表示闸英。 例如“Url編碼”,使用UTF-8編碼得到的字節(jié)是0x55 0x72 0x6C 0xE7 0xBC 0x96 0xE7 0xA0 0x81膊爪,由于前三個(gè)字節(jié)對(duì)應(yīng)著ASCII中的非保留字符“Url”自阱,因此這三個(gè)字節(jié)可以用非保留字符“Url”表示。最終的Url編碼可以簡(jiǎn)化成 “Url%E7%BC%96%E7%A0%81” 米酬,當(dāng)然沛豌,如果你用"%55%72%6C%E7%BC%96%E7%A0%81”也是可以的。
準(zhǔn)備知識(shí)到這兒就差不多了,下面進(jìn)入我們這章的正題:AFURLRequestSerialization
/**
把請(qǐng)求參數(shù)按照前面講的百分號(hào)編碼方式進(jìn)行編碼
*/
FOUNDATION_EXPORT NSString * AFPercentEscapedStringFromString(NSString *string);
/**
請(qǐng)求參數(shù)序列化方法加派,會(huì)先把參數(shù)用用上面的方法編碼叫确,然后把這些參數(shù)照著key=value&key=value的方式設(shè)置。
*/
FOUNDATION_EXPORT NSString * AFQueryStringFromParameters(NSDictionary *parameters);
上面是兩個(gè)序列化方面的函數(shù)芍锦,接下來(lái)就是兩個(gè)協(xié)議
/**
這個(gè)協(xié)議主要是request對(duì)象用來(lái)設(shè)置請(qǐng)求頭竹勉、請(qǐng)求體、查詢字符串的時(shí)候使用
例如娄琉,一個(gè)JSON的request要將HTTP主體設(shè)置為JSON表示次乓,并將HTTP的“Content-Type”頭域設(shè)置為“application/ JSON”。
*/
@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying>
/**
這個(gè)實(shí)現(xiàn)這個(gè)協(xié)議目的的方法
*/
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end
/**
這個(gè)協(xié)議是在multipart/form-data請(qǐng)求中調(diào)用的孽水,一般的get和post請(qǐng)求用不到票腰。
主要是用于外部調(diào)用AFHTTPRequestSerializer 中multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:方法獲取request的時(shí)候,對(duì)constructingBodyWithBlock參數(shù)的支持女气。
實(shí)際上是AFStreamingMultipartFormData遵守了這個(gè)協(xié)議杏慰,所以就實(shí)現(xiàn)了下面這些方法,外部就可以通過constructingBodyWithBlock的參數(shù)的AFStreamingMultipartFormData對(duì)象調(diào)用下面這些方法炼鞠。
*/
@protocol AFMultipartFormData
/**
根據(jù)文件路徑和請(qǐng)求參數(shù)名來(lái)添加請(qǐng)求參數(shù)缘滥,文件名和MIME類型根據(jù)文件路徑獲取,出現(xiàn)錯(cuò)誤就在error里描述谒主。
設(shè)置請(qǐng)求頭` Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`朝扼,后面都一樣,就不寫了瘩将。
*/
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
error:(NSError * _Nullable __autoreleasing *)error;
/**
根據(jù)文件路徑吟税、參數(shù)名凹耙、文件名姿现、mimeType來(lái)添加請(qǐng)求參數(shù)
*/
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType
error:(NSError * _Nullable __autoreleasing *)error;
/**
根據(jù)傳入的流文件、參數(shù)名肖抱、文件名备典、長(zhǎng)度、mimeType等參數(shù)來(lái)設(shè)置request請(qǐng)求參數(shù)意述。
*/
- (void)appendPartWithInputStream:(nullable NSInputStream *)inputStream
name:(NSString *)name
fileName:(NSString *)fileName
length:(int64_t)length
mimeType:(NSString *)mimeType;
/**
根據(jù)傳入的data數(shù)據(jù)提佣、參數(shù)名、文件名荤崇、mimeType等參數(shù)來(lái)設(shè)置request請(qǐng)求參數(shù)拌屏。
*/
- (void)appendPartWithFileData:(NSData *)data
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType;
/**
根據(jù)傳入的data數(shù)據(jù)、參數(shù)名等參數(shù)來(lái)設(shè)置request請(qǐng)求參數(shù)术荤。
不設(shè)置filename和content-type倚喂,是這樣的:`Content-Disposition: form-data; name=#{name}"`
*/
- (void)appendPartWithFormData:(NSData *)data
name:(NSString *)name;
/**
添加自定義的頭文件和已經(jīng)編碼過的request body來(lái)設(shè)置請(qǐng)求參數(shù)
*/
- (void)appendPartWithHeaders:(nullable NSDictionary <NSString *, NSString *> *)headers
body:(NSData *)body;
/**
限制數(shù)據(jù)包的大小和設(shè)置一個(gè)延時(shí)讀取。
是為了防止你數(shù)據(jù)包內(nèi)容過大瓣戚,超過了請(qǐng)求體正文的大小端圈,而導(dǎo)致失敗焦读。
*/
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
delay:(NSTimeInterval)delay;
@end
這一塊就是針對(duì)multipart/form-data
請(qǐng)求時(shí),添加文件舱权、流矗晃、data等參數(shù)時(shí)調(diào)用的。AFStreamingMultipartFormData類會(huì)遵守這個(gè)協(xié)議宴倍,然后把AFStreamingMultipartFormData對(duì)象作為獲取請(qǐng)求方法(例如:multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:)的參數(shù)张症,然后AFStreamingMultipartFormData對(duì)象參數(shù)會(huì)把添加的參數(shù)組裝成AFHTTPBodyPart對(duì)象添加到AFMultipartBodyStream對(duì)象中,然后把AFMultipartBodyStream對(duì)象設(shè)置為request的bodyStream鸵贬。
所以吠冤,實(shí)質(zhì)上實(shí)現(xiàn)了AFMultipartFormData協(xié)議的AFStreamingMultipartFormData對(duì)象是作為中間人,把參數(shù)添加到AFMultipartBodyStream對(duì)象中恭理。
接著看AFHTTPRequestSerializer拯辙,這就是對(duì)NSMutableURLRequest的一些基本設(shè)置和獲取NSMutableURLRequest的方法。
@interface AFHTTPRequestSerializer : NSObject <AFURLRequestSerialization>
/**
編碼方式
*/
@property (nonatomic, assign) NSStringEncoding stringEncoding;
/**
是否允許使用設(shè)備的蜂窩移動(dòng)網(wǎng)絡(luò)來(lái)創(chuàng)建request颜价,默認(rèn)為YES
*/
@property (nonatomic, assign) BOOL allowsCellularAccess;
/**
創(chuàng)建的request所使用的緩存策略涯保,默認(rèn)使用`NSURLRequestUseProtocolCachePolicy`,該策略表示
如果緩存不存在周伦,直接從服務(wù)端獲取夕春。如果緩存存在,會(huì)根據(jù)response中的Cache-Control字段判斷
下一步操作专挪,如: Cache-Control字段為must-revalidata, 則 詢問服務(wù)端該數(shù)據(jù)是否有更新及志,無(wú)更新話
直接返回給用戶緩存數(shù)據(jù),若已更新寨腔,則請(qǐng)求服務(wù)端.
*/
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
/**
HTTPShouldHandleCookies表示是否應(yīng)該給request設(shè)置cookie并隨request一起發(fā)送出去速侈,默認(rèn)是YES
*/
@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;
/**
HTTPShouldUsePipelining表示receiver(理解為iOS客戶端)的下一個(gè)信息是否必須等到上一個(gè)請(qǐng)求回復(fù)才能發(fā)送,默認(rèn)是NO迫卢。
*/
@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;
/**
設(shè)定request的network service類型. 默認(rèn)是`NSURLNetworkServiceTypeDefault`.
這個(gè)network service是為了告訴系統(tǒng)網(wǎng)絡(luò)層這個(gè)request使用的目的
比如NSURLNetworkServiceTypeVoIP表示的就這個(gè)request是用來(lái)請(qǐng)求網(wǎng)際協(xié)議通話技術(shù)(Voice over IP)倚搬。
系統(tǒng)能根據(jù)提供的信息來(lái)優(yōu)化網(wǎng)絡(luò)處理,從而優(yōu)化電池壽命乾蛤,網(wǎng)絡(luò)性能等等
*/
@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;
/**
超時(shí)機(jī)制每界,默認(rèn)60秒
*/
@property (nonatomic, assign) NSTimeInterval timeoutInterval;
///---------------------------------------
/// 請(qǐng)求頭相關(guān)配置
///---------------------------------------
/**
獲取http請(qǐng)求頭的屬性,默認(rèn)情況下包含以下字段:
- `Accept-Language` 接收語(yǔ)言
- `User-Agent` 帶有各種bundle標(biāo)識(shí)符和操作系統(tǒng)名稱的內(nèi)容
只讀屬性家卖,如果需要添加或者刪除需要調(diào)用`setValue:
forHTTPHeaderField: `.
*/
@property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;
/**
返回一個(gè)默認(rèn)配置的序列化對(duì)象
*/
+ (instancetype)serializer;
/**
設(shè)置http請(qǐng)求頭眨层,如果value是nil,就刪除已經(jīng)存在的字段上荡。
*/
- (void)setValue:(nullable NSString *)value
forHTTPHeaderField:(NSString *)field;
/**
獲取http請(qǐng)求頭對(duì)應(yīng)的頭域的值趴樱。
*/
- (nullable NSString *)valueForHTTPHeaderField:(NSString *)field;
/**
對(duì)用戶名和密碼用":"拼接,然后進(jìn)行base 64編碼,設(shè)置Basic Authorization頭域
*/
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
password:(NSString *)password;
/**
清空Authorization的請(qǐng)求頭內(nèi)容
*/
- (void)clearAuthorizationHeader;
///-------------------------------------------------------
/// query字符串參數(shù)序列化的配置
///-------------------------------------------------------
/**
需要把參數(shù)拼接到url后面的方法集合伊佃,默認(rèn)包括 `GET`, `HEAD`和 `DELETE`
*/
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;
/**
根據(jù)預(yù)定義的方式設(shè)置序列化的方法
*/
- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style;
/**
設(shè)置一個(gè)自定義的序列化方法block窜司,在序列化request的時(shí)候如果有自定義的方法,會(huì)優(yōu)先調(diào)用航揉,否則才調(diào)用默認(rèn)的塞祈。
定義將參數(shù)編碼到查詢字符串過程中的塊。此塊返回查詢字符串并接受三個(gè)參數(shù):請(qǐng)求帅涂、要編碼的參數(shù)议薪,以及在試圖為給定請(qǐng)求編碼參數(shù)時(shí)發(fā)生的錯(cuò)誤。
*/
- (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;
///-------------------------------
/// 創(chuàng)建一個(gè)request對(duì)象
///-------------------------------
/**
根據(jù)指定的HTTP方法和URL字符串創(chuàng)建一個(gè)`NSMutableUrlRequest `對(duì)象媳友。
如果HTTP方法是“get”斯议、“head”或“delete”,則這些參數(shù)將用于構(gòu)造一個(gè)URL編碼的query字符串醇锚,該字符串被追加到請(qǐng)求的URL中哼御。
否則,根據(jù) `parameterencoding `屬性的值進(jìn)行編碼焊唬,并設(shè)置為請(qǐng)求體恋昼。
*/
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error;
/**
通過構(gòu)建`multipart/form-data`請(qǐng)求體來(lái)創(chuàng)建NSMutableURLRequest
@param block 一個(gè)帶有實(shí)現(xiàn)了<AFMultipartFormData>協(xié)議的對(duì)象的block,用于向`multipart/form-data`類型的請(qǐng)求體中添加數(shù)據(jù)赶促。
上面講AFMultipartFormData協(xié)議的時(shí)候就講過了液肌,實(shí)現(xiàn)了< AFMultipartFormData >協(xié)議的AFStreamingMultipartFormData類作為中間人,
把參數(shù)組裝為AFHTTPBodyPart類鸥滨,然后把AFHTTPBodyPart類的實(shí)體對(duì)象加到AFMultipartBodyStream中嗦哆。AFMultipartBodyStream就是http請(qǐng)求體。
*/
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable NSDictionary <NSString *, id> *)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
error:(NSError * _Nullable __autoreleasing *)error;
/**
這個(gè)方法會(huì)把request的HTTPBodyStream寫如果指定文件婿滓,然后返回一個(gè)刪除HTTPBodyStream之后的request老速。
這樣做為了修復(fù)一個(gè)bug--在與Amazon S3服務(wù)器交互時(shí)無(wú)法發(fā)送Content-Length頭域.
被寫入HTTPBodyStream的文件將會(huì)被`AFURLSessionManager`的`uploadTaskWithRequest:fromFile:progress:completionHandler:`發(fā)送或者以data的形式作為HTTPBody被發(fā)送。
*/
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
writingStreamContentsToFile:(NSURL *)fileURL
completionHandler:(nullable void (^)(NSError * _Nullable error))handler;
@end
加下來(lái)就是正文了空幻,看看前面這些方法都是怎么實(shí)現(xiàn)的烁峭,用到了哪些知識(shí)點(diǎn)。
NSString * AFPercentEscapedStringFromString(NSString *string) {
//需要做百分號(hào)編碼處理的字符串秕铛,前面講過的
static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // RFC 3986文檔允許"?"、"/"可以不用編碼
static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
//獲取系統(tǒng)中不需要百分號(hào)編碼的字符集合
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
//刪除RFC 3986文檔規(guī)定要做編碼的字符缩挑,剩下的就是最終不需要做編碼的字符集合
[allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
// FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
// return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
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);
// 獲取真正的range但两,防止特殊字符被分開編碼
range = [string rangeOfComposedCharacterSequencesForRange:range];
NSString *substring = [string substringWithRange:range];
//對(duì)除去allowedCharacterSet字符集合之外的字符進(jìn)行編碼
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
[escaped appendString:encoded];
index += range.length;
}
return escaped;
}
大部分知識(shí)點(diǎn)注釋都解釋清楚了啊,有一個(gè)rangeOfComposedCharacterSequencesForRange :
不是很清楚啊供置,可以看一下我之前的關(guān)于字符串真正的range的講解谨湘。
@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;
}
- (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
這個(gè)內(nèi)部類主要是為了方便把鍵值對(duì)進(jìn)行百分號(hào)編碼之后用"="連接起來(lái),很簡(jiǎn)單,也沒什么知識(shí)點(diǎn)紧阔,自己看看就好坊罢。
//把參數(shù)編碼之后編碼為用&符連接起來(lái)
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
//把數(shù)組中的元素用&鏈接起來(lái)
return [mutablePairs componentsJoinedByString:@"&"];
}
//把字典轉(zhuǎn)化為AFQueryStringPair對(duì)象數(shù)組
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
//把key-value鍵值對(duì)轉(zhuǎn)化為AFQueryStringPair對(duì)象數(shù)組
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
//創(chuàng)建一個(gè)排序?qū)ο? NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
// 按照sortDescriptor排序所有的key,在反序列化有歧義的序列時(shí)時(shí)比較重要的
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;
}
都很簡(jiǎn)單的知識(shí)點(diǎn)擅耽,這幾個(gè)函數(shù)是用于普通的get或者set方法的參數(shù)的序列化,AFQueryStringPairsFromKeyAndValue函數(shù)把參數(shù)轉(zhuǎn)化為AFQueryStringPair對(duì)象的數(shù)組,然后AFQueryStringFromParameters函數(shù)就調(diào)用間接AFQueryStringPairsFromKeyAndValue函數(shù)獲取AFQueryStringPair對(duì)象數(shù)組绍傲,把每個(gè)對(duì)象進(jìn)行url編碼之后规哲,用&符連接起來(lái)生成一個(gè)字符串就完成了。
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è)就是指定了request請(qǐng)求序列化要觀察的屬性列表乃沙、是一個(gè)數(shù)組起趾,里面有對(duì)蜂窩數(shù)據(jù)、緩存策略警儒、cookie训裆、管道、網(wǎng)絡(luò)狀態(tài)蜀铲、超時(shí)這幾個(gè)元素缭保,返回一個(gè)數(shù)組對(duì)象。
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.stringEncoding = NSUTF8StringEncoding;
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
/*
枚舉系統(tǒng)的language列表蝙茶。然后設(shè)置`Accept-Language`請(qǐng)求頭域艺骂。優(yōu)先級(jí)逐級(jí)降低,最多五個(gè)隆夯。
*/
// Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
[[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
float q = 1.0f - (idx * 0.1f);
[acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
*stop = q <= 0.5f;
}];
//數(shù)組元素使用`, `分割
[self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];
NSString *userAgent = nil;
#if TARGET_OS_IOS
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
#elif TARGET_OS_WATCH
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
#endif
if (userAgent) {
if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
NSMutableString *mutableUserAgent = [userAgent mutableCopy];
if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
userAgent = mutableUserAgent;
}
}
[self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
}
//默認(rèn)參數(shù)需要追加到url query后面的的方法
// HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];
//對(duì)序列化時(shí)需要觀察的屬性添加觀察者
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
return self;
}
- (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
[self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
_allowsCellularAccess = allowsCellularAccess;
[self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
}
- (void)setCachePolicy:(NSURLRequestCachePolicy)cachePolicy {
[self willChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))];
_cachePolicy = cachePolicy;
[self didChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))];
}
......
初始化方法钳恕,設(shè)置一些默認(rèn)值,然后對(duì)需要觀察的屬性添加觀察者蹄衷,在后面是需要觀察的屬性的setter方法忧额,手動(dòng)觸發(fā)kvo屬性。
中間那么一些方法都很簡(jiǎn)單愧口,自己看看睦番,就不寫了。
接下來(lái)就是這章的重點(diǎn)耍属,構(gòu)建NSMutableRequest
#pragma mark -
- (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);
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
/**
手動(dòng)觸發(fā)需要觀察的屬性的kvo
*/
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
//根據(jù)創(chuàng)建的request和參數(shù)調(diào)用下面的方法創(chuàng)建一個(gè)最終的request
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
#pragma mark - AFURLRequestSerialization
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
//用枚舉的方式設(shè)置請(qǐng)求頭
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
NSString *query = nil;
if (parameters) {
//如果有自定義的序列化方法就調(diào)用自定義的
if (self.queryStringSerialization) {
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
} else {
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
//默認(rèn)的序列化方式
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
//`GET`托嚣、`HEAD`、`DELET`三種方式是需要把參數(shù)追加到query字符串后面
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {
//一般的POST等方式就設(shè)置為HTTPBody
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
//設(shè)置Content-Type頭域
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
上面的方法就是一般情況下的請(qǐng)求(非multipart表單請(qǐng)求)走第一個(gè)方法厚骗,然后調(diào)用AFURLRequestSerialization協(xié)議進(jìn)行參數(shù)序列化示启,拼接query或者設(shè)置HTTPBody,這就是一般情況下的request领舰,就構(gòu)建完成了夫嗓。
接下來(lái)就是我們的重點(diǎn)內(nèi)容迟螺,multipart表單請(qǐng)求,還是先來(lái)個(gè)例子給大家看一下
//開始
--A4CditForm07uar02yZ34WTsOi5HI0YtW7
Content-Disposition: form-data; name="data"; filename="abc.png"
Content-Type: application/octet-stream
......//數(shù)據(jù)
--A4CditForm07uar02yZ34WTsOi5HI0YtW7
Content-Disposition: form-data; name="Upload"
Submit Query
--A4CditForm07uar02yZ34WTsOi5HI0YtW7--// 表示body結(jié)束了
這就是一個(gè)multipart表單請(qǐng)求的格式舍咖,先有一個(gè)整體的概念矩父,下面再詳細(xì)講怎么把參數(shù)構(gòu)建為這種格式。
#pragma mark -
//第一個(gè)AFHTTPPart表單構(gòu)建初始邊界排霉,即報(bào)文主體的初始邊界
static NSString * AFCreateMultipartFormBoundary() {
return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
}
static NSString * const kAFMultipartFormCRLF = @"\r\n";
//非第一個(gè)AFHTTPPart表單構(gòu)建開始邊界
static inline NSString * AFMultipartFormInitialBoundary(NSString *boundary) {
return [NSString stringWithFormat:@"--%@%@", boundary, kAFMultipartFormCRLF];
}
//非最后一個(gè)AFHTTPPart表單構(gòu)建結(jié)束邊界
static inline NSString * AFMultipartFormEncapsulationBoundary(NSString *boundary) {
return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}
//最后一個(gè)AFHTTPPart表單構(gòu)建結(jié)束邊界
static inline NSString * AFMultipartFormFinalBoundary(NSString *boundary) {
return [NSString stringWithFormat:@"%@--%@--%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}
//根據(jù)文件擴(kuò)展獲取MIME類型
static inline NSString * AFContentTypeForPathExtension(NSString *extension) {
//根據(jù)擴(kuò)展名獲取UTI同一類型標(biāo)識(shí)符
NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
//UTI同一類型標(biāo)識(shí)符轉(zhuǎn)換為MIME類型
NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
if (!contentType) {
return @"application/octet-stream";
} else {
return contentType;
}
}
這就是表單邊界的構(gòu)建方法和MIME類型的獲取
//序列化multipart表單參數(shù)的四個(gè)階段
typedef enum {
//開始邊界階段
AFEncapsulationBoundaryPhase = 1,
//表單頭階段
AFHeaderPhase = 2,
//表單主體階段
AFBodyPhase = 3,
//表單結(jié)束階段
AFFinalBoundaryPhase = 4,
} AFHTTPBodyPartReadPhase;
@interface AFHTTPBodyPart () <NSCopying> {
AFHTTPBodyPartReadPhase _phase;
NSInputStream *_inputStream;
unsigned long long _phaseReadOffset;
}
//進(jìn)入下一階段
- (BOOL)transitionToNextPhase;
//讀取數(shù)據(jù)流
- (NSInteger)readData:(NSData *)data
intoBuffer:(uint8_t *)buffer
maxLength:(NSUInteger)length;
@end
@implementation AFHTTPBodyPart
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
//參數(shù)序列化進(jìn)入初始階段
[self transitionToNextPhase];
return self;
}
- (void)dealloc {
if (_inputStream) {
[_inputStream close];
_inputStream = nil;
}
}
//獲取主體數(shù)據(jù)流
- (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;
}
//multipart表單頭的字符串
- (NSString *)stringForHeaders {
NSMutableString *headerString = [NSMutableString string];
for (NSString *field in [self.headers allKeys]) {
[headerString appendString:[NSString stringWithFormat:@"%@: %@%@", field, [self.headers valueForKey:field], kAFMultipartFormCRLF]];
}
[headerString appendString:kAFMultipartFormCRLF];
return [NSString stringWithString:headerString];
}
//表單大小
- (unsigned long long)contentLength {
unsigned long long length = 0;
//讀取初始邊界長(zhǎng)度
NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding];
length += [encapsulationBoundaryData length];
//讀取表單頭長(zhǎng)度
NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding];
length += [headersData length];
//表單主體長(zhǎng)度
length += _bodyContentLength;
// 表單結(jié)束長(zhǎng)度
NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]);
length += [closingBoundaryData length];
return length;
}
//是否有數(shù)據(jù)可讀
- (BOOL)hasBytesAvailable {
// Allows `read:maxLength:` to be called again if `AFMultipartFormFinalBoundary` doesn't fit into the available buffer
if (_phase == AFFinalBoundaryPhase) {
return YES;
}
//數(shù)據(jù)流還沒打開窍株、已經(jīng)打開、正在讀郑诺、正在寫夹姥,都代表還有數(shù)據(jù)可以讀
//數(shù)據(jù)流已經(jīng)結(jié)束、已經(jīng)關(guān)閉辙诞、出現(xiàn)錯(cuò)誤表示沒有數(shù)據(jù)可以讀了
switch (self.inputStream.streamStatus) {
case NSStreamStatusNotOpen:
case NSStreamStatusOpening:
case NSStreamStatusOpen:
case NSStreamStatusReading:
case NSStreamStatusWriting:
return YES;
case NSStreamStatusAtEnd:
case NSStreamStatusClosed:
case NSStreamStatusError:
default:
return NO;
}
}
//上傳時(shí)需要讀區(qū)數(shù)據(jù)的時(shí)候辙售,AFMultipartBodyStream調(diào)用此方法
- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length
{
NSInteger totalNumberOfBytesRead = 0;
//初始階段,構(gòu)建初始邊界
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)];
}
//表單頭階段:構(gòu)建表單頭
if (_phase == AFHeaderPhase) {
//設(shè)置的表單頭域進(jìn)行編碼
NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding];
//編碼后的表單頭域?qū)懭霐?shù)據(jù)流
totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
}
//表單主體階段:設(shè)置表單主體
if (_phase == AFBodyPhase) {
NSInteger numberOfBytesRead = 0;
//把數(shù)據(jù)流的內(nèi)容寫入
numberOfBytesRead = [self.inputStream read:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
if (numberOfBytesRead == -1) {
return -1;
} else {
totalNumberOfBytesRead += numberOfBytesRead;
//判斷數(shù)據(jù)流是否寫入完成飞涂,結(jié)束后進(jìn)入下一階段
if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) {
[self transitionToNextPhase];
}
}
}
//結(jié)束階段:構(gòu)建結(jié)束邊界
if (_phase == AFFinalBoundaryPhase) {
//構(gòu)建結(jié)束邊界
NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]);
//結(jié)束邊界寫入數(shù)據(jù)流
totalNumberOfBytesRead += [self readData:closingBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
}
return totalNumberOfBytesRead;
}
//判斷數(shù)據(jù)的大小是否超過了表單允許的最大數(shù)值旦部,選取兩個(gè)較小的一個(gè)
- (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;
//如果讀入的數(shù)據(jù)已經(jīng)超過了
if (((NSUInteger)_phaseReadOffset) >= [data length]) {
[self transitionToNextPhase];
}
return (NSInteger)range.length;
}
//進(jìn)入下一階段
- (BOOL)transitionToNextPhase {
if (![[NSThread currentThread] isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^{
[self transitionToNextPhase];
});
return YES;
}
switch (_phase) {
case AFEncapsulationBoundaryPhase:
_phase = AFHeaderPhase;
break;
case AFHeaderPhase:
//要進(jìn)入下一階段(AFBodyPhase),先打開數(shù)據(jù)流,把數(shù)據(jù)流加入runloop的偽模式
[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;
break;
}
_phaseReadOffset = 0;
return YES;
}
AFHTTPBodyPart類较店,該有的注釋都有了士八,具體的就不在這兒多解釋了,一個(gè)AFHTTPBodyPart對(duì)象就是一個(gè)表單數(shù)據(jù)梁呈,需要上傳數(shù)據(jù)的時(shí)候就調(diào)用read:maxLength:
方法讀取婚度,然后調(diào)用contentLength
獲取數(shù)據(jù)長(zhǎng)度。
//繼承自NSInputStream
@interface AFMultipartBodyStream () <NSCopying>
//編碼方式
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;
//裝在AFHTTPBodyPart對(duì)象的數(shù)組
@property (readwrite, nonatomic, strong) NSMutableArray *HTTPBodyParts;
//AFHTTPBodyPart對(duì)象的迭代器官卡,方便迭代
@property (readwrite, nonatomic, strong) NSEnumerator *HTTPBodyPartEnumerator;
//當(dāng)前的AFHTTPBodyPart對(duì)象
@property (readwrite, nonatomic, strong) AFHTTPBodyPart *currentHTTPBodyPart;
@property (readwrite, nonatomic, strong) NSOutputStream *outputStream;
@property (readwrite, nonatomic, strong) NSMutableData *buffer;
@end
@implementation AFMultipartBodyStream
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1100)
@synthesize delegate;
#endif
@synthesize streamStatus;
@synthesize streamError;
- (instancetype)initWithStringEncoding:(NSStringEncoding)encoding {
self = [super init];
if (!self) {
return nil;
}
self.stringEncoding = encoding;
self.HTTPBodyParts = [NSMutableArray array];
self.numberOfBytesInPacket = NSIntegerMax;
return self;
}
//設(shè)置初始邊界和結(jié)束邊界
- (void)setInitialAndFinalBoundaries {
if ([self.HTTPBodyParts count] > 0) {
for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) {
bodyPart.hasInitialBoundary = NO;
bodyPart.hasFinalBoundary = NO;
}
//第一個(gè)AFHTTPBodyPart對(duì)象設(shè)置初始邊界
[[self.HTTPBodyParts firstObject] setHasInitialBoundary:YES];
//最后一個(gè)設(shè)置結(jié)束邊界
[[self.HTTPBodyParts lastObject] setHasFinalBoundary:YES];
}
}
//把AFHTTPBodyPart對(duì)象加入到當(dāng)前對(duì)象的HTTPBodyParts數(shù)組中
- (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart {
[self.HTTPBodyParts addObject:bodyPart];
}
- (BOOL)isEmpty {
return [self.HTTPBodyParts count] == 0;
}
#pragma mark - NSInputStream
//在上傳數(shù)據(jù)的時(shí)候系統(tǒng)調(diào)用該方法獲取數(shù)據(jù)流
- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length
{
if ([self streamStatus] == NSStreamStatusClosed) {
return 0;
}
NSInteger totalNumberOfBytesRead = 0;
//讀入數(shù)據(jù)
while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
break;
}
} else {
//去當(dāng)前參數(shù)大小和數(shù)據(jù)包最大值中較小的的一個(gè)作為數(shù)據(jù)包大小
NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
//調(diào)用AFHTTPBodyPart對(duì)象的read:maxLength:方法讀取數(shù)據(jù)
NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
//讀取數(shù)據(jù)是否出錯(cuò)
if (numberOfBytesRead == -1) {
self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
break;
} else {
totalNumberOfBytesRead += numberOfBytesRead;
//判斷是否需要延時(shí)操作
if (self.delay > 0.0f) {
[NSThread sleepForTimeInterval:self.delay];
}
}
}
}
NSLog(@"buffer : %s contentLength: %zd", buffer, length);
return totalNumberOfBytesRead;
}
- (BOOL)getBuffer:(__unused uint8_t **)buffer
length:(__unused NSUInteger *)len
{
return NO;
}
//是否還有可讀數(shù)據(jù)
- (BOOL)hasBytesAvailable {
return [self streamStatus] == NSStreamStatusOpen;
}
AFMultipartBodyStream對(duì)象在網(wǎng)絡(luò)請(qǐng)求發(fā)送報(bào)文的時(shí)候以流的形式作為報(bào)文主體蝗茁,它的內(nèi)部包含多個(gè)AFHTTPBodyPart對(duì)象,系統(tǒng)讀取AFMultipartBodyStream對(duì)象的數(shù)據(jù)時(shí)寻咒,AFMultipartBodyStream對(duì)象就會(huì)去讀取AFHTTPBodyPart對(duì)象的數(shù)據(jù)哮翘。
AFMultipartBodyStream對(duì)象設(shè)置了哪些AFHTTPBodyPart是需要設(shè)置開始邊界,哪些需要設(shè)置結(jié)束邊界毛秘,還規(guī)定了一個(gè)表單的最大長(zhǎng)度等數(shù)據(jù)饭寺。
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
error:(NSError * __autoreleasing *)error
{
NSParameterAssert(fileURL);
NSParameterAssert(name);
NSString *fileName = [fileURL lastPathComponent];
//根據(jù)文件擴(kuò)展名MIME類型
NSString *mimeType = AFContentTypeForPathExtension([fileURL pathExtension]);
return [self appendPartWithFileURL:fileURL name:name fileName:fileName mimeType:mimeType error:error];
}
- (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);
//文件路徑出錯(cuò)
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;
}
//設(shè)置表單Content-Disposition和Content-Type頭域
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
[mutableHeaders setValue:mimeType forKey:@"Content-Type"];
//構(gòu)建AFHTTPBodyPart對(duì)象表單
AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
bodyPart.stringEncoding = self.stringEncoding;
bodyPart.headers = mutableHeaders;
bodyPart.boundary = self.boundary;
bodyPart.body = fileURL;
bodyPart.bodyContentLength = [fileAttributes[NSFileSize] unsignedLongLongValue];
//把AFHTTPBodyPart對(duì)象裝載到AFMultipartBodyStream對(duì)象中
[self.bodyStream appendHTTPBodyPart:bodyPart];
return YES;
}
//和上面的一樣,就不寫了
.....
/**
限制數(shù)據(jù)包的大小和設(shè)置一個(gè)延時(shí)讀取叫挟。
是為了防止你數(shù)據(jù)包內(nèi)容過大艰匙,超過了請(qǐng)求體正文的大小,而導(dǎo)致失敗
*/
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
delay:(NSTimeInterval)delay
{
self.bodyStream.numberOfBytesInPacket = numberOfBytes;
self.bodyStream.delay = delay;
}
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
if ([self.bodyStream isEmpty]) {
return self.request;
}
// 重制初始邊界和結(jié)束邊界以便獲取正確的報(bào)文大小
[self.bodyStream setInitialAndFinalBoundaries];
//把裝載AFTHTTPPart對(duì)象裝載完成后的AFMultipartBodyStream對(duì)象設(shè)置為報(bào)文主體
[self.request setHTTPBodyStream:self.bodyStream];
//設(shè)置請(qǐng)求的Content-Type和Content-Length頭域
[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;
}
AFStreamingMultipartFormData類就是一個(gè)中間人霞揉,外部參數(shù)通過這個(gè)類把構(gòu)建為AFHTTPBodyPart對(duì)象旬薯,然后把AFHTTPBodyPart寫入到AFMultipartBodyStream對(duì)象數(shù)據(jù)流中,在參數(shù)寫入數(shù)據(jù)流完成之后調(diào)用requestByFinalizingMultipartFormData方法來(lái)設(shè)置請(qǐng)求體适秩,最后設(shè)置請(qǐng)求頭類型和請(qǐng)求體大小绊序,request創(chuàng)建完成。