AFNetworking知識(shí)點(diǎn)之AFURLRequestSerialization

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)建完成。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末秽荞,一起剝皮案震驚了整個(gè)濱河市骤公,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扬跋,老刑警劉巖阶捆,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異钦听,居然都是意外死亡洒试,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門朴上,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)垒棋,“玉大人,你說我怎么就攤上這事痪宰〉鸺埽” “怎么了?”我有些...
    開封第一講書人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵衣撬,是天一觀的道長(zhǎng)乖订。 經(jīng)常有香客問我,道長(zhǎng)具练,這世上最難降的妖魔是什么乍构? 我笑而不...
    開封第一講書人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮扛点,結(jié)果婚禮上哥遮,老公的妹妹穿的比我還像新娘。我一直安慰自己占键,他們只是感情好昔善,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著畔乙,像睡著了一般君仆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上牲距,一...
    開封第一講書人閱讀 49,842評(píng)論 1 290
  • 那天返咱,我揣著相機(jī)與錄音,去河邊找鬼牍鞠。 笑死咖摹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的难述。 我是一名探鬼主播萤晴,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼吐句,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了店读?” 一聲冷哼從身側(cè)響起嗦枢,我...
    開封第一講書人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎屯断,沒想到半個(gè)月后文虏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡殖演,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年氧秘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趴久。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡丸相,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出朋鞍,到底是詐尸還是另有隱情已添,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布滥酥,位于F島的核電站更舞,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏坎吻。R本人自食惡果不足惜缆蝉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瘦真。 院中可真熱鬧刊头,春花似錦、人聲如沸诸尽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)您机。三九已至穿肄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間际看,已是汗流浹背咸产。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留仲闽,地道東北人脑溢。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像赖欣,于是被迫代替她去往敵國(guó)和親屑彻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子验庙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)酱酬,斷路器壶谒,智...
    卡卡羅2017閱讀 134,633評(píng)論 18 139
  • 編碼問題一直困擾著開發(fā)人員云矫,尤其在 Java 中更加明顯膳沽,因?yàn)?Java 是跨平臺(tái)語(yǔ)言,不同平臺(tái)之間編碼之間的切換...
    x360閱讀 2,470評(píng)論 1 20
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法让禀,類相關(guān)的語(yǔ)法挑社,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法巡揍,異常的語(yǔ)法痛阻,線程的語(yǔ)...
    子非魚_t_閱讀 31,598評(píng)論 18 399
  • 整體Retrofit內(nèi)容如下: 1、Retrofit解析1之前哨站——理解RESTful2腮敌、Retrofit解析2...
    隔壁老李頭閱讀 15,060評(píng)論 4 39
  • ??你是否有過這樣的經(jīng)歷: 明明心里想的是把這個(gè)矛盾解決糜工,可是卻不經(jīng)意間出口傷人弊添? 明明只是想表達(dá)下自己的想法,可...
    六秒啊閱讀 418評(píng)論 0 1