iOS源碼補(bǔ)完計(jì)劃--AFNetworking(四)

目錄

  • 前言
  • 流程圖
  • 核心代碼
  • 請(qǐng)求頭
  • 請(qǐng)求體
    • 1、通過(guò)Parameters(參數(shù)字典)構(gòu)建的請(qǐng)求體
      • 1、用戶自定義block轉(zhuǎn)譯
      • 2、AFN默認(rèn)的轉(zhuǎn)義方式:
        • 將字典轉(zhuǎn)化成字符串并且轉(zhuǎn)譯
        • 根據(jù)不同情況將參數(shù)字符串拼接到URL后、或者放入請(qǐng)求體
    • 2、通過(guò)文件構(gòu)建的請(qǐng)求體
      • AFMultipartFormData協(xié)議
      • AFHTTPBodyPart
      • AFStreamingMultipartFormData
        • 核心方法(將文件分段拼接)
  • AFHTTPRequestSerializer
    • AFHTTPRequestSerializer.h
    • AFHTTPRequestSerializer.m
      • NSInputStream和NSOutputSteam的使用
  • APIDemo
  • 參考資料

前言

AFNetworking源碼第四篇
主要看了看AFURLRequestSerialization的內(nèi)容
負(fù)責(zé)網(wǎng)絡(luò)請(qǐng)求NSMutableURLRequest對(duì)象的初始化
以及請(qǐng)求頭、請(qǐng)求體、參數(shù)、上傳文件的自動(dòng)化配置

幾千行代碼、很長(zhǎng)。但是讀下來(lái)會(huì)受益匪淺

AFN概述:《iOS源碼補(bǔ)完計(jì)劃--AFNetworking 3.1.0源碼研讀》

流程圖

里面的文件實(shí)在是太多了、還是先弄個(gè)流程圖好一些?


AFHTTPRequestSerializer流程圖

解釋圖中幾個(gè)相關(guān)的類:

  • AFHTTPRequestSerializer
    負(fù)責(zé)請(qǐng)求的生產(chǎn)
  • AFQueryStringPair
    內(nèi)部有兩個(gè)屬性。分別代表字典的key和value。
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;

在將請(qǐng)求參數(shù)拼接進(jìn)URL或請(qǐng)求體的時(shí)候會(huì)用到。

  • AFHTTPBodyPart
    請(qǐng)求體單個(gè)片段(也就是單個(gè)name)
  • AFStreamingMultipartFormData
    用來(lái)整合請(qǐng)求體信息、并且整合進(jìn)request
  • AFMultipartFormData
    負(fù)責(zé)上傳文件拼接的協(xié)議、AFStreamingMultipartFormData對(duì)象就遵從這個(gè)協(xié)議。內(nèi)部將會(huì)先轉(zhuǎn)化成AFHTTPBodyPart然后追加給AFStreamingMultipartFormData
    包括追加文件、二進(jìn)制文件、數(shù)據(jù)流等等。

核心代碼

還是先寫(xiě)一個(gè)普通的請(qǐng)求代碼:

AFHTTPRequestSerializer * requestSerializer =  [AFHTTPRequestSerializer serializer];
[requestSerializer setValue:@"請(qǐng)求頭value1" forHTTPHeaderField:@"請(qǐng)求頭key1"];
[requestSerializer setValue:@"請(qǐng)求頭value2" forHTTPHeaderField:@"請(qǐng)求頭key2"];

NSMutableURLRequest * req = [requestSerializer requestWithMethod:@"POST" URLString:@"http://127.0.0.1:3000/" parameters:@{@"key":@"value"} error:nil];

[[[AFHTTPSessionManager manager] dataTaskWithRequest:req completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
    
}] resume] ;

AFN內(nèi)部的操作很直觀
1、生成一個(gè)_MRequest
2、如果你設(shè)置了一些通用屬性、則覆蓋一下。
3、設(shè)置請(qǐng)求頭請(qǐng)求體

//通過(guò)請(qǐng)求方式、URL、參數(shù)字典生成請(qǐng)求
- (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];
    //設(shè)置請(qǐng)求方式
    mutableRequest.HTTPMethod = method;

    //如果某個(gè)關(guān)鍵屬性被自主設(shè)置過(guò)、則用新的。不然直接用模板生成的`NSMutableURLRequest`即可
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }

    //對(duì)req進(jìn)一步設(shè)置(拼接URL、請(qǐng)求體、請(qǐng)求頭)
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

    return mutableRequest;
}

//監(jiān)聽(tīng)集合。蜂窩網(wǎng)絡(luò)、緩存策略、cookie、管線鏈接、網(wǎng)絡(luò)服務(wù)類型、超時(shí)連接
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;
}

第三步mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];尤為重要、它完善了整個(gè)request的必要信息。

- (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];
        }
    }];

    //根據(jù)參數(shù)parameters設(shè)置查詢字段
    NSString *query = nil;
    if (parameters) {
        //看看參數(shù)是否需要用戶自定義轉(zhuǎn)譯
        if (self.queryStringSerialization) {
            NSError *serializationError;
            //調(diào)用用戶block、獲得參數(shù)轉(zhuǎn)譯的字符串
            query = self.queryStringSerialization(request, parameters, &serializationError);

            if (serializationError) {
                if (error) {
                    *error = serializationError;
                }

                return nil;
            }
        } else {
            //使用AFN的默認(rèn)轉(zhuǎn)譯方式
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }

    //如果請(qǐng)求方式是需要將查詢參數(shù)拼接到URL后面的(默認(rèn)包含`GET``HEAD``DELETE`)、則拼接
    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 {
        //否則、則放入請(qǐng)求體
        // #2864: an empty string is a valid x-www-form-urlencoded payload
        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;
}

至此一個(gè)正常的請(qǐng)求就結(jié)束了。
不過(guò)、既然是讀源碼、肯定還需要把每個(gè)代碼都好好看看。


請(qǐng)求頭

請(qǐng)求頭可以通過(guò)外界設(shè)置、保存在字典里。在生成request的時(shí)候批量添加設(shè)置進(jìn)去。

/*
    Get方法
 */
- (NSDictionary *)HTTPRequestHeaders {
    //將不可變字典轉(zhuǎn)換成可變字典
    return [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
}
/*
    設(shè)置請(qǐng)求頭
 */
- (void)setValue:(NSString *)value
forHTTPHeaderField:(NSString *)field
{
    //對(duì)請(qǐng)求頭字典進(jìn)行追加
    [self.mutableHTTPRequestHeaders setValue:value forKey:field];
}

- (NSString *)valueForHTTPHeaderField:(NSString *)field {
    //根據(jù)不同的key提取出value
    return [self.mutableHTTPRequestHeaders valueForKey:field];
}

//通過(guò)賬號(hào)密碼設(shè)置授權(quán)請(qǐng)求頭
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
                                       password:(NSString *)password
{
    //轉(zhuǎn)化成data
    NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding];
    //base64編碼加密
    NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
    //加入請(qǐng)求頭字典
    [self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"];
}
//清除授權(quán)用請(qǐng)求頭
- (void)clearAuthorizationHeader {
    [self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"];
}

針對(duì)AFN而不是multipart 協(xié)議來(lái)看、可以講的沒(méi)有多少。


請(qǐng)求體

  • 1、通過(guò)Parameters(參數(shù)字典)構(gòu)建的請(qǐng)求體

既然提到Parameters、那么就像上面代碼里寫(xiě)的一樣、所有的Parameters字典、都需要被轉(zhuǎn)義成字符串。

AFN為我們提供了兩種轉(zhuǎn)換的方式。

  • 1、用戶自定義block轉(zhuǎn)譯

沒(méi)啥說(shuō)的、參數(shù)都給你。自己轉(zhuǎn)譯完還給AFN字符串就行

- (void)setQueryStringSerializationWithBlock:(NSString *(^)(NSURLRequest *, id, NSError *__autoreleasing *))block {
    self.queryStringSerialization = block;
}
  • 2、AFN默認(rèn)的轉(zhuǎn)義方式:
switch (self.queryStringSerializationStyle) {
    case AFHTTPRequestQueryStringDefaultStyle:
        query = AFQueryStringFromParameters(parameters);
        break;
}

雖然是個(gè)switch、但是queryStringSerializationStyle默認(rèn)目前只被提供了一種:

typedef NS_ENUM(NSUInteger, AFHTTPRequestQueryStringSerializationStyle) {
    AFHTTPRequestQueryStringDefaultStyle = 0,
};

但是寫(xiě)成枚舉、是為了將來(lái)更好的擴(kuò)展和維護(hù)。像這種有可能擴(kuò)展的地方、是我們值得借鑒的。

  • 將字典轉(zhuǎn)化成字符串并且轉(zhuǎn)譯
//將字典參數(shù)轉(zhuǎn)化成字符串
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        //將AFQueryStringPair對(duì)象轉(zhuǎn)化成key=value的格式并且傳遞給新的數(shù)組
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }
    //開(kāi)頭添加"&"
    return [mutablePairs componentsJoinedByString:@"&"];
}

//將字典轉(zhuǎn)化成數(shù)組{key1[key2]value}
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
    return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}

//把key && value 轉(zhuǎn)成數(shù)組
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
    NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

    //排序 升序
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];

    if ([value isKindOfClass:[NSDictionary class]]) {
        //參數(shù)value為字典
        
        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
        
        /*
            [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]
            對(duì)所有的字典里的nestedKey進(jìn)行排序
         */
        for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            //獲取nestedKey里的value
            id nestedValue = dictionary[nestedKey];
            if (nestedValue) {
                if (key) {
                    //如果指明了key、則為二級(jí)字典。用key[nestedKey]進(jìn)行遍歷
                    //比如@{aaa:@{bbb:ccc}};就會(huì)被變成aaa[bbb]作為key
                    [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[%@]", key, nestedKey], nestedValue)];
                }else {
                    //如果沒(méi)有傳入nestedKey、則直接為一級(jí)字典。用subkey進(jìn)行遍歷
                    [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(nestedKey, 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 {
        //如果是字符串、則將key、value組合
        //如果是字典的話就會(huì)出現(xiàn)key = @"aaaa[bbbb]" value = @"cccc"這種情況、后面會(huì)再轉(zhuǎn)化
        [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
    }

    //返回最終的`AFQueryStringPair`對(duì)象數(shù)組
    return mutableQueryStringComponents;
}
  • 字典轉(zhuǎn)字符串
    NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value)
    利用遞歸的方式對(duì)深層次的參數(shù)(value是NSDictionary/NSArray/NSSet)進(jìn)行拆分。
  • AFQueryStringPair對(duì)象
@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

用來(lái)儲(chǔ)存每個(gè)field(key)對(duì)應(yīng)的value。
比如@{aaa:@{bbb:ccc}};就會(huì)被變成aaa[bbb]作為field

  • 轉(zhuǎn)譯方法
    將每個(gè)AFQueryStringPair轉(zhuǎn)譯成字符串[pair URLEncodedStringValue]

如果有value則使用key=value、否則直接轉(zhuǎn)譯key

- (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])];
    }
}

RFC 3986 規(guī)范下需要被保留的字符
":", "#", "[", "]", "@", "?", "/"
"!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
在RFC 3986 規(guī)范 Section 3.4 下 的查詢字段、除了"?","/"以外的特殊字符都應(yīng)該被轉(zhuǎn)義。所以有了以下這個(gè)方法:

/**
 對(duì)字符串編碼
 */
NSString * AFPercentEscapedStringFromString(NSString *string) {
    //":", "#", "[", "]", "@", "?", "/"  除去"?","/"
    static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
    
    //"!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
    static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";

    
    //將以上兩種設(shè)置為排除
    NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
    /*
        只是[NSCharacterSet URLQueryAllowedCharacterSet]的話以上字符默認(rèn)是不轉(zhuǎn)譯的
        需要移出去。只保留"?"和"/"不轉(zhuǎn)譯
    */
    [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];

    // FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
    // return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];

    //每次最多轉(zhuǎn)譯50個(gè)字符
    static NSUInteger const batchSize = 50;

    NSUInteger index = 0;
    NSMutableString *escaped = @"".mutableCopy;

    while (index < string.length) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wgnu"
        NSUInteger length = MIN(string.length - index, batchSize);
#pragma GCC diagnostic pop
        NSRange range = NSMakeRange(index, length);

        // To avoid breaking up character sequences such as ????????
        range = [string rangeOfComposedCharacterSequencesForRange:range];
        
        NSString *substring = [string substringWithRange:range];
        
        NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
        [escaped appendString:encoded];

        index += range.length;
    }

    return escaped;
}
  • 根據(jù)不同情況將參數(shù)字符串拼接到URL后、或者放入請(qǐng)求體
//如果請(qǐng)求方式是需要將查詢參數(shù)拼接到URL后面的(默認(rèn)包含`GET``HEAD``DELETE`)、則拼接
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
    if (query && query.length > 0) {
        //原url帶有參數(shù)、則拼接'&'。沒(méi)參數(shù)則拼接'?'
        mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
    }
} else {
    //否則、則放入請(qǐng)求體
    // #2864: an empty string is a valid x-www-form-urlencoded payload
    if (!query) {
        query = @"";
    }
    if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
        [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    }
    [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}

是否拼接到URL取決于HTTPMethodsEncodingParametersInURI這個(gè)屬性。
用戶可以自定義追加或者刪除這個(gè)合集的內(nèi)容。

/**
    需要拼接參數(shù)的請(qǐng)求方法、默認(rèn)包含`GET``HEAD``DELETE`
 */
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;
  • 2、通過(guò)文件構(gòu)建的請(qǐng)求體
NSMutableURLRequest * request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"baidu" parameters:parameter constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
    
    NSString *documentFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSURL *fileURL = [NSURL fileURLWithPath:[documentFolder stringByAppendingPathComponent:@"1.txt"]];
    [formData appendPartWithFileURL:fileURL name:@"userfile[]" error:NULL];
    NSURL *fileURL1 = [NSURL fileURLWithPath:[documentFolder stringByAppendingPathComponent:@"2.jpg"]];
    [formData appendPartWithFileURL:fileURL1 name:@"userfile[]" fileName:@"aaa.jpg" mimeType:@"image/jpeg" error:NULL];
    
} error:nil];
  • AFMultipartFormData協(xié)議
    文件的方式主要通過(guò)協(xié)議AFMultipartFormData進(jìn)行、有很多添加方式。
@protocol AFMultipartFormData

- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                         name:(NSString *)name
                        error:(NSError * _Nullable __autoreleasing *)error;

- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                         name:(NSString *)name
                     fileName:(NSString *)fileName
                     mimeType:(NSString *)mimeType
                        error:(NSError * _Nullable __autoreleasing *)error;
- (void)appendPartWithInputStream:(nullable NSInputStream *)inputStream
                             name:(NSString *)name
                         fileName:(NSString *)fileName
                           length:(int64_t)length
                         mimeType:(NSString *)mimeType;

- (void)appendPartWithFileData:(NSData *)data
                          name:(NSString *)name
                      fileName:(NSString *)fileName
                      mimeType:(NSString *)mimeType;

- (void)appendPartWithFormData:(NSData *)data
                          name:(NSString *)name;

- (void)appendPartWithHeaders:(nullable NSDictionary <NSString *, NSString *> *)headers
                         body:(NSData *)body;

- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
                                  delay:(NSTimeInterval)delay;

@end

在進(jìn)行必要的Content-Type生成后、都會(huì)流入以下方法:

AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
bodyPart.stringEncoding = self.stringEncoding;
bodyPart.headers = headers;
bodyPart.boundary = self.boundary;
bodyPart.bodyContentLength = [body length];
bodyPart.body = body;

[self.bodyStream appendHTTPBodyPart:bodyPart];

AFHTTPBodyPart實(shí)例、追加進(jìn)self.bodyStream(AFStreamingMultipartFormData實(shí)例)

  • AFHTTPBodyPart

單個(gè)請(qǐng)求體文件

@interface AFHTTPBodyPart : NSObject
@property (nonatomic, assign) NSStringEncoding stringEncoding;//編碼
@property (nonatomic, strong) NSDictionary *headers;//頭信息
@property (nonatomic, copy) NSString *boundary;//邊界
@property (nonatomic, strong) id body;//內(nèi)容
@property (nonatomic, assign) unsigned long long bodyContentLength;//內(nèi)容大小
@property (nonatomic, strong) NSInputStream *inputStream;//流

@property (nonatomic, assign) BOOL hasInitialBoundary;//是否有初始邊界
@property (nonatomic, assign) BOOL hasFinalBoundary;//是否有結(jié)束邊界

@property (readonly, nonatomic, assign, getter = hasBytesAvailable) BOOL bytesAvailable;//body是否存在
@property (readonly, nonatomic, assign) unsigned long long contentLength;//長(zhǎng)度

//讀取數(shù)據(jù)
- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length;
@end
  • AFStreamingMultipartFormData
@interface AFStreamingMultipartFormData ()
@property (readwrite, nonatomic, copy) NSMutableURLRequest *request;//請(qǐng)求request
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;//編碼
@property (readwrite, nonatomic, copy) NSString *boundary;//邊界
@property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream;//body文件整合工具
@end

繼承自NSInputStream、因?yàn)樽詈笮枰獙⒄麄€(gè)實(shí)例接入給request.bodyStream。

- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
    if ([self.bodyStream isEmpty]) {
        //如果body為空、則直接返回request
        return self.request;
    }

    // Reset the initial and final boundaries to ensure correct Content-Length
    [self.bodyStream setInitialAndFinalBoundaries];
    //將請(qǐng)求體接入request
    [self.request setHTTPBodyStream:self.bodyStream];
    //multipart協(xié)議請(qǐng)求頭
    [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;
}

我們注意到有兩個(gè)請(qǐng)求頭的設(shè)置、是multipart協(xié)議獨(dú)有的。
其中、self.bodyStream是我們正在說(shuō)的AFStreamingMultipartFormData實(shí)例。
self.boundary是邊界分隔符。
AFStreamingMultipartFormData初始化時(shí)一并初始完成。

@implementation AFStreamingMultipartFormData

- (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest
                    stringEncoding:(NSStringEncoding)encoding
{
    self = [super init];
    if (!self) {
        return nil;
    }

    self.request = urlRequest;
    self.stringEncoding = encoding;
    self.boundary = AFCreateMultipartFormBoundary();
    self.bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding];

    return self;
}

//其中
static NSString * AFCreateMultipartFormBoundary() {
    return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
}

初次之外、還有三個(gè)函數(shù)

//如果是開(kāi)頭分隔符的,那么只需在分隔符結(jié)尾加一個(gè)換行符
static inline NSString * AFMultipartFormInitialBoundary(NSString *boundary) {
    return [NSString stringWithFormat:@"--%@%@", boundary, kAFMultipartFormCRLF];
}
//如果是中間部分分隔符,那么需要分隔符前面和結(jié)尾都加換行符
static inline NSString * AFMultipartFormEncapsulationBoundary(NSString *boundary) {
    return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}
//如果是末尾,還得使用--分隔符--作為請(qǐng)求體的結(jié)束標(biāo)志
static inline NSString * AFMultipartFormFinalBoundary(NSString *boundary) {
    return [NSString stringWithFormat:@"%@--%@--%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}

static inline NSString * AFContentTypeForPathExtension(NSString *extension) {
    NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
    NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
    if (!contentType) {
        return @"application/octet-stream";
    } else {
        return contentType;
    }
}

服務(wù)于第二個(gè)請(qǐng)求頭[self.bodyStream contentLength]

//計(jì)算body大小
- (unsigned long long)contentLength {

    unsigned long long length = 0;
    
    //初始邊界  是否有初始邊界?初始邊界:封裝邊界(比初始邊界多了個(gè)開(kāi)頭的\r\n)
    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];

    //內(nèi)容長(zhǎng)度
    length += _bodyContentLength;

    //結(jié)束邊界 是否有結(jié)束邊界?結(jié)束邊界:0
    NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]);
    length += [closingBoundaryData length];

    return length;
}

初始化、設(shè)置邊界、追加body文件、驗(yàn)證有效性

- (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;
        }

        [[self.HTTPBodyParts firstObject] setHasInitialBoundary:YES];
        [[self.HTTPBodyParts lastObject] setHasFinalBoundary:YES];
    }
}

//追加body文件
- (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart {
    [self.HTTPBodyParts addObject:bodyPart];
}

//是否為空
- (BOOL)isEmpty {
    return [self.HTTPBodyParts count] == 0;
}
  • 核心方法(將文件分段拼接)

之所以是核心方法、因?yàn)檫@是AFN的一個(gè)亮點(diǎn)。

平時(shí)在用NSURLRequest上傳文件時(shí),一般是兩種方法:
1、一個(gè)是設(shè)置body、但是如果文件稍大的話,將會(huì)撐爆內(nèi)存。
2、另外一種則是創(chuàng)建一個(gè)臨時(shí)文件、將數(shù)據(jù)拼接進(jìn)去、然后將文件路徑設(shè)置為bodyStream、這樣就可以分片的上傳了。

而AFN這種方式無(wú)疑更好、具體來(lái)看:

- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length
{
    if ([self streamStatus] == NSStreamStatusClosed) {
        return 0;
    }
    
    //總大小
    NSInteger totalNumberOfBytesRead = 0;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    //遍歷讀取數(shù)據(jù)
    while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
        
        //body不存在或者沒(méi)有可讀字節(jié)
        if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
            //把下一個(gè)body文件賦值給當(dāng)前body
            if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
                break;
            }
        } else {
            //存在可讀數(shù)據(jù)
            
            NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
            
            //把當(dāng)前body讀取到buffer中。內(nèi)部會(huì)采用遞歸的方式將數(shù)據(jù)分段寫(xiě)入buffer
            NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
            
            if (numberOfBytesRead == -1) {
                self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
                break;
            } else {
                totalNumberOfBytesRead += numberOfBytesRead;

                if (self.delay > 0.0f) {
                    [NSThread sleepForTimeInterval:self.delay];
                }
            }
        }
    }
#pragma clang diagnostic pop

    return totalNumberOfBytesRead;
}

AFHTTPRequestSerializer

其實(shí)到這已經(jīng)沒(méi)什么特別難得東西了、只剩下一些流程任務(wù)

  • AFHTTPRequestSerializer.h
@interface AFHTTPRequestSerializer : NSObject <AFURLRequestSerialization>

/**
    編碼方式。默認(rèn)`NSUTF8StringEncoding`
 */
@property (nonatomic, assign) NSStringEncoding stringEncoding;

/**
    允許蜂窩網(wǎng)絡(luò)
 */
@property (nonatomic, assign) BOOL allowsCellularAccess;

/**
    請(qǐng)求的緩存策略
 
     typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
     {
     //默認(rèn)策略。緩存存在則根據(jù)request中Cache-Control字段進(jìn)行不同操作、否則則請(qǐng)求
     NSURLRequestUseProtocolCachePolicy = 0,
     //每次都請(qǐng)求服務(wù)器
     NSURLRequestReloadIgnoringLocalCacheData = 1,
     //忽略本地緩存和中間代理。直接請(qǐng)求原服務(wù)器 -- 未實(shí)現(xiàn)
     NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented
     //同NSURLRequestReloadIgnoringLocalCacheData
     NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
     //有緩存就是用、不管其有效性。即忽略Cache-Control字段、沒(méi)有就訪問(wèn)源server
     NSURLRequestReturnCacheDataElseLoad = 2,
     //只加載本地?cái)?shù)據(jù),不做其他操作,適用于沒(méi)有網(wǎng)路的情況
     NSURLRequestReturnCacheDataDontLoad = 3,
     //緩存數(shù)據(jù)必須得到服務(wù)器確認(rèn)才能使用--未實(shí)現(xiàn)
     NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented
 };

 */
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;

/**
    是否使用默認(rèn)的cookie處理、默認(rèn)YES
 */
@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;

/**
    管線化傳輸、默認(rèn)NO
    正常的HTTP請(qǐng)求都是一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)連接、每次TCP鏈接需要一定的時(shí)間。
    管線化:允許一次發(fā)送一組請(qǐng)求而不必等到響應(yīng)。但并不是所有服務(wù)器都支持、需要和服務(wù)器協(xié)商。并且需要提前建立好鏈接才能使用
 */
@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;

/**
    網(wǎng)絡(luò)服務(wù)類型
     typedef NS_ENUM(NSUInteger, NSURLRequestNetworkServiceType)
     {
     NSURLNetworkServiceTypeDefault = 0, // 普通網(wǎng)絡(luò)傳輸,默認(rèn)使用這個(gè)
     NSURLNetworkServiceTypeVoIP = 1,    // 網(wǎng)絡(luò)語(yǔ)音通信傳輸,只能在VoIP使用
     NSURLNetworkServiceTypeVideo = 2,   // 影像傳輸
     NSURLNetworkServiceTypeBackground = 3, // 網(wǎng)絡(luò)后臺(tái)傳輸,優(yōu)先級(jí)不高時(shí)可使用。對(duì)用戶不需要的網(wǎng)絡(luò)操作可使用
     NSURLNetworkServiceTypeVoice = 4       // 語(yǔ)音傳輸
     };
 */
@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;

/**
     超時(shí)時(shí)間
 */
@property (nonatomic, assign) NSTimeInterval timeoutInterval;

///---------------------------------------
/// @name Configuring HTTP Request Headers
///---------------------------------------

/**
     請(qǐng)求頭
 */
@property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;

/**
    初始化一個(gè)默認(rèn)配置的req
 */
+ (instancetype)serializer;

/**
    設(shè)置httpheader、如果value為nil、則移除field(key)
 */
- (void)setValue:(nullable NSString *)value
forHTTPHeaderField:(NSString *)field;

/**
    返回field(key)標(biāo)記的內(nèi)容
 */
- (nullable NSString *)valueForHTTPHeaderField:(NSString *)field;

/**
    設(shè)置請(qǐng)求頭中的`Authorization`字段
 */
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
                                       password:(NSString *)password;

/**
    清空請(qǐng)求頭
 */
- (void)clearAuthorizationHeader;

///-------------------------------------------------------
/// @name 請(qǐng)求參數(shù)配置
///-------------------------------------------------------

/**
    需要拼接參數(shù)的請(qǐng)求方法、默認(rèn)包含`GET``HEAD``DELETE`
 */
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;

/**
    查詢參數(shù)的轉(zhuǎn)義樣式.(目前只有一種)
 */
- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style;

/**
    你也可以自定義參數(shù)的轉(zhuǎn)義方式
 */
- (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;

///-------------------------------
///   核心方法
///-------------------------------

/**
    如果請(qǐng)求方式為GET`, `HEAD`, or `DELETE時(shí)、參數(shù)會(huì)被拼接到URL中、否則當(dāng)做body處理
 */
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(nullable id)parameters
                                     error:(NSError * _Nullable __autoreleasing *)error;

/**
    支持上傳數(shù)據(jù)
 */
- (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è)方法可以把一個(gè)帶有bodystream請(qǐng)求體的req、轉(zhuǎn)化成不含bodystream的req。
    其中的文件會(huì)被轉(zhuǎn)寫(xiě)到fileURL的路徑下。
    你可以通過(guò)upload直接上傳
    NSURLSessionTask中有一個(gè)bug,當(dāng)HTTP body的內(nèi)容是來(lái)自NSStream的時(shí)候,request無(wú)法發(fā)送Content-Length到服務(wù)器端,此問(wèn)題在Amazon S3的Web服務(wù)中尤為顯著。
 */
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
                             writingStreamContentsToFile:(NSURL *)fileURL
                                       completionHandler:(nullable void (^)(NSError * _Nullable error))handler;

@end
  • AFHTTPRequestSerializer.m
@interface AFHTTPRequestSerializer ()
@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;//記錄修改過(guò)的屬性
@property (readwrite, nonatomic, strong) NSMutableDictionary *mutableHTTPRequestHeaders;//請(qǐng)求頭字典
@property (readwrite, nonatomic, assign) AFHTTPRequestQueryStringSerializationStyle queryStringSerializationStyle;//參數(shù)轉(zhuǎn)義的樣式
@property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization;//自定義參數(shù)轉(zhuǎn)義block
@end

@implementation AFHTTPRequestSerializer

+ (instancetype)serializer {
    return [[self alloc] init];
}

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }

    self.stringEncoding = NSUTF8StringEncoding;

    self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];

    // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
    /**
     *  傳遞可接受的語(yǔ)言,q代表對(duì)語(yǔ)言的喜好程度,默認(rèn)是取出前5個(gè)的數(shù)據(jù),不足5個(gè),取實(shí)際的個(gè)數(shù)
     */
    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;
    }];
    //放到請(qǐng)求頭里
    [self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];

    //獲取用戶信息
    NSString *userAgent = nil;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#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
#pragma clang diagnostic pop
    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;
            }
        }
        //將用戶信息寫(xiě)入請(qǐng)求頭
        [self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
    }

    // HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
    self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];

    self.mutableObservedChangedKeyPaths = [NSMutableSet set];
    //批量添加監(jiān)聽(tīng)
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
            [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
        }
    }

    return self;
}

- (void)dealloc {
    //移除監(jiān)聽(tīng)
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
            [self removeObserver:self forKeyPath:keyPath context:AFHTTPRequestSerializerObserverContext];
        }
    }
}

#pragma mark -

// Workarounds for crashing behavior using Key-Value Observing with XCTest
// See https://github.com/AFNetworking/AFNetworking/issues/2523

- (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))];
}

- (void)setHTTPShouldHandleCookies:(BOOL)HTTPShouldHandleCookies {
    [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
    _HTTPShouldHandleCookies = HTTPShouldHandleCookies;
    [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
}

- (void)setHTTPShouldUsePipelining:(BOOL)HTTPShouldUsePipelining {
    [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))];
    _HTTPShouldUsePipelining = HTTPShouldUsePipelining;
    [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))];
}

- (void)setNetworkServiceType:(NSURLRequestNetworkServiceType)networkServiceType {
    [self willChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))];
    _networkServiceType = networkServiceType;
    [self didChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))];
}

- (void)setTimeoutInterval:(NSTimeInterval)timeoutInterval {
    [self willChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))];
    _timeoutInterval = timeoutInterval;
    [self didChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))];
}

#pragma mark -

/*
    對(duì)請(qǐng)求頭進(jìn)行操作
 */
- (NSDictionary *)HTTPRequestHeaders {
    //將不可變字典轉(zhuǎn)換成可變字典
    return [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
}

- (void)setValue:(NSString *)value
forHTTPHeaderField:(NSString *)field
{
    //對(duì)請(qǐng)求頭字典進(jìn)行追加
    [self.mutableHTTPRequestHeaders setValue:value forKey:field];
}

- (NSString *)valueForHTTPHeaderField:(NSString *)field {
    //根據(jù)不同的key提取出value
    return [self.mutableHTTPRequestHeaders valueForKey:field];
}

//通過(guò)賬號(hào)密碼設(shè)置授權(quán)請(qǐng)求頭
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
                                       password:(NSString *)password
{
    //轉(zhuǎn)化成data
    NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding];
    //base64編碼加密
    NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
    //加入請(qǐng)求頭字典
    [self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"];
}

//清除授權(quán)用請(qǐng)求頭
- (void)clearAuthorizationHeader {
    [self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"];
}

#pragma mark -
//設(shè)置查詢參數(shù)的編碼方式
- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style {
    self.queryStringSerializationStyle = style;
    self.queryStringSerialization = nil;
}

//設(shè)置查詢參數(shù)自定義編碼的block
- (void)setQueryStringSerializationWithBlock:(NSString *(^)(NSURLRequest *, id, NSError *__autoreleasing *))block {
    self.queryStringSerialization = block;
}

#pragma mark -

//通過(guò)請(qǐng)求方式、URL、參數(shù)字典生成請(qǐng)求
- (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];
    //設(shè)置請(qǐng)求方式
    mutableRequest.HTTPMethod = method;

    //如果某個(gè)關(guān)鍵屬性被自主設(shè)置過(guò)、則用新的。不然直接用模板生成的`NSMutableURLRequest`即可
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }

    //對(duì)req進(jìn)一步設(shè)置(拼接URL、請(qǐng)求體、請(qǐng)求頭)
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

    return mutableRequest;
}

/*
    數(shù)據(jù)上傳專用處理
    不允許GET&&HEAD方法
    參數(shù)將會(huì)拼接到formdata中以表單的形式上傳
 */
- (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"]);

    //生成req
    NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];

    // 創(chuàng)建一個(gè)`AFStreamingMultipartFormData`實(shí)例,用來(lái)處理數(shù)據(jù)。
    __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];

    if (parameters) {
        //遍歷參數(shù)字典(轉(zhuǎn)化成的數(shù)組)
        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) {
                //將每個(gè)參數(shù)拼接到`AFStreamingMultipartFormData`對(duì)象中
                [formData appendPartWithFormData:data name:[pair.field description]];
            }
        }
    }

    if (block) {
        block(formData);
    }
    //將請(qǐng)求體數(shù)據(jù)接入請(qǐng)求、并且返回處理完成的req
    return [formData requestByFinalizingMultipartFormData];
}

//將請(qǐng)求體中的文件、剝離出來(lái)
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
                             writingStreamContentsToFile:(NSURL *)fileURL
                                       completionHandler:(void (^)(NSError *error))handler
{
    //是否有HTTPBodyStream
    NSParameterAssert(request.HTTPBodyStream);
    //是否是個(gè)文件
    NSParameterAssert([fileURL isFileURL]);

    //獲取寫(xiě)入對(duì)象
    NSInputStream *inputStream = request.HTTPBodyStream;
   // 使用outputStream將HTTPBodyStream的內(nèi)容寫(xiě)入到路徑為fileURL的文件中
    NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO];
    __block NSError *error = nil;

    //異步寫(xiě)入
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //開(kāi)啟讀寫(xiě)工具
        [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

        [inputStream open];
        [outputStream open];

        
        //讀取數(shù)據(jù) `hasBytesAvailable`代表是否有可用字節(jié)
        while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) {
            uint8_t buffer[1024];
            //將數(shù)據(jù)讀取到buffer、每次最大讀取1024
            NSInteger bytesRead = [inputStream read:buffer maxLength:1024];
            if (inputStream.streamError || bytesRead < 0) {
                error = inputStream.streamError;
                break;
            }
            
            //將buffer寫(xiě)入
            NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead];
            if (outputStream.streamError || bytesWritten < 0) {
                error = outputStream.streamError;
                break;
            }
            
            //如果讀取與寫(xiě)入都為0、代表轉(zhuǎn)寫(xiě)結(jié)束
            if (bytesRead == 0 && bytesWritten == 0) {
                break;
            }
        }

        [outputStream close];
        [inputStream close];

        if (handler) {
            dispatch_async(dispatch_get_main_queue(), ^{
                handler(error);
            });
        }
    });

    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    mutableRequest.HTTPBodyStream = nil;

    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];
        }
    }];

    //根據(jù)參數(shù)parameters設(shè)置查詢字段
    NSString *query = nil;
    if (parameters) {
        //看看參數(shù)是否需要用戶自定義轉(zhuǎn)譯
        if (self.queryStringSerialization) {
            NSError *serializationError;
            //調(diào)用用戶block、獲得參數(shù)轉(zhuǎn)譯的字符串
            query = self.queryStringSerialization(request, parameters, &serializationError);

            if (serializationError) {
                if (error) {
                    *error = serializationError;
                }

                return nil;
            }
        } else {
            //使用AFN的默認(rèn)轉(zhuǎn)譯方式
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }

    //如果請(qǐng)求方式是需要將查詢參數(shù)拼接到URL后面的(默認(rèn)包含`GET``HEAD``DELETE`)、則拼接
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query && query.length > 0) {
            //原url帶有參數(shù)、則拼接'&'。沒(méi)參數(shù)則拼接'?'
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
        }
    } else {
        //否則、則放入請(qǐng)求體
        // #2864: an empty string is a valid x-www-form-urlencoded payload
        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;
}

#pragma mark - NSKeyValueObserving
//可以決定是否發(fā)送KVO通知
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) {
        return NO;
    }

    return [super automaticallyNotifiesObserversForKey:key];
}


- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(__unused id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if (context == AFHTTPRequestSerializerObserverContext) {
        if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
            //如果沒(méi)有新值、則清空所屬監(jiān)聽(tīng)
            [self.mutableObservedChangedKeyPaths removeObject:keyPath];
        } else {
            [self.mutableObservedChangedKeyPaths addObject:keyPath];
        }
    }
}

#pragma mark - NSSecureCoding

+ (BOOL)supportsSecureCoding {
    return YES;
}

- (instancetype)initWithCoder:(NSCoder *)decoder {
    self = [self init];
    if (!self) {
        return nil;
    }

    self.mutableHTTPRequestHeaders = [[decoder decodeObjectOfClass:[NSDictionary class] forKey:NSStringFromSelector(@selector(mutableHTTPRequestHeaders))] mutableCopy];
    self.queryStringSerializationStyle = (AFHTTPRequestQueryStringSerializationStyle)[[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(queryStringSerializationStyle))] unsignedIntegerValue];

    return self;
}

- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject:self.mutableHTTPRequestHeaders forKey:NSStringFromSelector(@selector(mutableHTTPRequestHeaders))];
    [coder encodeInteger:self.queryStringSerializationStyle forKey:NSStringFromSelector(@selector(queryStringSerializationStyle))];
}

#pragma mark - NSCopying

- (instancetype)copyWithZone:(NSZone *)zone {
    AFHTTPRequestSerializer *serializer = [[[self class] allocWithZone:zone] init];
    serializer.mutableHTTPRequestHeaders = [self.mutableHTTPRequestHeaders mutableCopyWithZone:zone];
    serializer.queryStringSerializationStyle = self.queryStringSerializationStyle;
    serializer.queryStringSerialization = self.queryStringSerialization;

    return serializer;
}

@end

#pragma mark - 請(qǐng)求體

//請(qǐng)求體拼接
static NSString * AFCreateMultipartFormBoundary() {
    return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
}

static NSString * const kAFMultipartFormCRLF = @"\r\n";

static inline NSString * AFMultipartFormInitialBoundary(NSString *boundary) {
    return [NSString stringWithFormat:@"--%@%@", boundary, kAFMultipartFormCRLF];
}

static inline NSString * AFMultipartFormEncapsulationBoundary(NSString *boundary) {
    return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}

static inline NSString * AFMultipartFormFinalBoundary(NSString *boundary) {
    return [NSString stringWithFormat:@"%@--%@--%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}

static inline NSString * AFContentTypeForPathExtension(NSString *extension) {
    NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
    NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
    if (!contentType) {
        return @"application/octet-stream";
    } else {
        return contentType;
    }
}

NSUInteger const kAFUploadStream3GSuggestedPacketSize = 1024 * 16;
NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2;


/**
    單個(gè)請(qǐng)求體文件
 */
@interface AFHTTPBodyPart : NSObject
@property (nonatomic, assign) NSStringEncoding stringEncoding;//編碼
@property (nonatomic, strong) NSDictionary *headers;//請(qǐng)求頭
@property (nonatomic, copy) NSString *boundary;//邊界
@property (nonatomic, strong) id body;//內(nèi)容
@property (nonatomic, assign) unsigned long long bodyContentLength;//內(nèi)容大小
@property (nonatomic, strong) NSInputStream *inputStream;//流

@property (nonatomic, assign) BOOL hasInitialBoundary;//是否有初始邊界
@property (nonatomic, assign) BOOL hasFinalBoundary;//是否有結(jié)束邊界

@property (readonly, nonatomic, assign, getter = hasBytesAvailable) BOOL bytesAvailable;//body是否存在
@property (readonly, nonatomic, assign) unsigned long long contentLength;//長(zhǎng)度

//讀取數(shù)據(jù)
- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length;
@end

//body文件整合工具
@interface AFMultipartBodyStream : NSInputStream <NSStreamDelegate>
@property (nonatomic, assign) NSUInteger numberOfBytesInPacket;//包大小
@property (nonatomic, assign) NSTimeInterval delay;//延遲
@property (nonatomic, strong) NSInputStream *inputStream;//輸入流
@property (readonly, nonatomic, assign) unsigned long long contentLength;//內(nèi)容大小
@property (readonly, nonatomic, assign, getter = isEmpty) BOOL empty;//是否為空

//初始化以及編碼方式
- (instancetype)initWithStringEncoding:(NSStringEncoding)encoding;
//設(shè)置請(qǐng)求體數(shù)據(jù)片段的初始邊界和結(jié)束邊界
- (void)setInitialAndFinalBoundaries;
//追加請(qǐng)求體數(shù)據(jù)
- (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart;
@end

#pragma mark -

//數(shù)據(jù)和request連接器
@interface AFStreamingMultipartFormData ()
@property (readwrite, nonatomic, copy) NSMutableURLRequest *request;//請(qǐng)求request
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;//編碼
@property (readwrite, nonatomic, copy) NSString *boundary;//邊界
@property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream;//body文件整合工具
@end

@implementation AFStreamingMultipartFormData

- (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest
                    stringEncoding:(NSStringEncoding)encoding
{
    self = [super init];
    if (!self) {
        return nil;
    }

    self.request = urlRequest;
    self.stringEncoding = encoding;
    self.boundary = AFCreateMultipartFormBoundary();
    self.bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding];

    return self;
}

- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                         name:(NSString *)name
                        error:(NSError * __autoreleasing *)error
{
    NSParameterAssert(fileURL);
    NSParameterAssert(name);

    //https://www.baidu.com/abc.html     結(jié)果就是abc.html
    NSString *fileName = [fileURL lastPathComponent];
    //文件類型
    NSString *mimeType = AFContentTypeForPathExtension([fileURL pathExtension]);

    return [self appendPartWithFileURL:fileURL name:name fileName:fileName mimeType:mimeType error:error];
}

#pragma mark - 將文件添加進(jìn)請(qǐng)求體
- (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);

    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;
    }

    NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
    //請(qǐng)求頭
    [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
    [mutableHeaders setValue:mimeType forKey:@"Content-Type"];

    //創(chuàng)建請(qǐng)求體模型、加入bodyStream準(zhǔn)備處理
    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;
}

#pragma mark - 將數(shù)據(jù)流添加進(jìn)請(qǐng)求體
- (void)appendPartWithInputStream:(NSInputStream *)inputStream
                             name:(NSString *)name
                         fileName:(NSString *)fileName
                           length:(int64_t)length
                         mimeType:(NSString *)mimeType
{
    NSParameterAssert(name);
    NSParameterAssert(fileName);
    NSParameterAssert(mimeType);

    NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
    [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
    [mutableHeaders setValue:mimeType forKey:@"Content-Type"];

    AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
    bodyPart.stringEncoding = self.stringEncoding;
    bodyPart.headers = mutableHeaders;
    bodyPart.boundary = self.boundary;
    bodyPart.body = inputStream;

    bodyPart.bodyContentLength = (unsigned long long)length;

    [self.bodyStream appendHTTPBodyPart:bodyPart];
}

#pragma mark - 通過(guò)二進(jìn)制文件設(shè)置請(qǐng)求體
- (void)appendPartWithFileData:(NSData *)data
                          name:(NSString *)name
                      fileName:(NSString *)fileName
                      mimeType:(NSString *)mimeType
{
    NSParameterAssert(name);
    NSParameterAssert(fileName);
    NSParameterAssert(mimeType);

    NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
    [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
    [mutableHeaders setValue:mimeType forKey:@"Content-Type"];

    [self appendPartWithHeaders:mutableHeaders body:data];
}


- (void)appendPartWithFormData:(NSData *)data
                          name:(NSString *)name
{
    NSParameterAssert(name);

    NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
    [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"];

    [self appendPartWithHeaders:mutableHeaders body:data];
}

- (void)appendPartWithHeaders:(NSDictionary *)headers
                         body:(NSData *)body
{
    NSParameterAssert(body);

    AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
    bodyPart.stringEncoding = self.stringEncoding;
    bodyPart.headers = headers;
    bodyPart.boundary = self.boundary;
    bodyPart.bodyContentLength = [body length];
    bodyPart.body = body;

    [self.bodyStream appendHTTPBodyPart:bodyPart];
}


- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
                                  delay:(NSTimeInterval)delay
{
    self.bodyStream.numberOfBytesInPacket = numberOfBytes;
    self.bodyStream.delay = delay;
}

#pragma mark - 將請(qǐng)求體數(shù)據(jù)接入請(qǐng)求
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
    if ([self.bodyStream isEmpty]) {
        //如果body為空、則直接返回request
        return self.request;
    }

    // Reset the initial and final boundaries to ensure correct Content-Length
    [self.bodyStream setInitialAndFinalBoundaries];
    //將請(qǐng)求體接入request
    [self.request setHTTPBodyStream:self.bodyStream];
    
    [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;
}

@end
  • NSInputStream和NSOutputSteam的使用

AFN的這個(gè)方法可以將請(qǐng)求體中的文件、剝離出來(lái)。具體怎么剝離呢?
就是將NSInputStream對(duì)象的內(nèi)容轉(zhuǎn)寫(xiě)到NSOutputSteam
和之前AFStreamingMultipartFormData的轉(zhuǎn)寫(xiě)方法相比、少了很多判斷、是最干凈的案例了。

- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
                             writingStreamContentsToFile:(NSURL *)fileURL
                                       completionHandler:(void (^)(NSError *error))handler
{
    //是否有HTTPBodyStream
    NSParameterAssert(request.HTTPBodyStream);
    //是否是個(gè)文件
    NSParameterAssert([fileURL isFileURL]);

    //獲取寫(xiě)入對(duì)象
    NSInputStream *inputStream = request.HTTPBodyStream;
   // 使用outputStream將HTTPBodyStream的內(nèi)容寫(xiě)入到路徑為fileURL的文件中
    NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO];
    __block NSError *error = nil;

    //異步寫(xiě)入
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //開(kāi)啟讀寫(xiě)工具
        [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

        [inputStream open];
        [outputStream open];

        
        //讀取數(shù)據(jù) `hasBytesAvailable`代表是否有可用字節(jié)
        while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) {
            uint8_t buffer[1024];
            //將數(shù)據(jù)讀取到buffer、每次最大讀取1024
            NSInteger bytesRead = [inputStream read:buffer maxLength:1024];
            if (inputStream.streamError || bytesRead < 0) {
                error = inputStream.streamError;
                break;
            }
            
            //將buffer寫(xiě)入
            NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead];
            if (outputStream.streamError || bytesWritten < 0) {
                error = outputStream.streamError;
                break;
            }
            
            //如果讀取與寫(xiě)入都為0、代表轉(zhuǎn)寫(xiě)結(jié)束
            if (bytesRead == 0 && bytesWritten == 0) {
                break;
            }
        }

        [outputStream close];
        [inputStream close];

        if (handler) {
            dispatch_async(dispatch_get_main_queue(), ^{
                handler(error);
            });
        }
    });

    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    mutableRequest.HTTPBodyStream = nil;

    return mutableRequest;
}

當(dāng)然、這個(gè)HttpBodySteam必須是通過(guò)AFN生成的AFMultipartBodyStream實(shí)例
才會(huì)執(zhí)行這個(gè)代理方法

- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length
{
    if ([self streamStatus] == NSStreamStatusClosed) {
        return 0;
    }
    
    //總大小
    NSInteger totalNumberOfBytesRead = 0;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    //遍歷讀取數(shù)據(jù)
    while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
        
        //body不存在或者沒(méi)有可讀字節(jié)
        if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
            //把下一個(gè)body文件賦值給當(dāng)前body
            if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
                break;
            }
        } else {
            //存在可讀數(shù)據(jù)
            
            NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
            
            //把當(dāng)前body讀取到buffer中。內(nèi)部會(huì)采用遞歸的方式將數(shù)據(jù)分段寫(xiě)入buffer
            NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
            
            if (numberOfBytesRead == -1) {
                self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
                break;
            } else {
                totalNumberOfBytesRead += numberOfBytesRead;

                if (self.delay > 0.0f) {
                    [NSThread sleepForTimeInterval:self.delay];
                }
            }
        }
    }
#pragma clang diagnostic pop

    return totalNumberOfBytesRead;
}

APIDemo

說(shuō)實(shí)話兩個(gè)文件加起來(lái)兩千多行、每行都想顧及到我自己都不知道怎么寫(xiě)...但是把很多代碼都進(jìn)行了注釋、有興趣可以自取:

GitHub


最后

本文主要是自己的學(xué)習(xí)與總結(jié)。如果文內(nèi)存在紕漏、萬(wàn)望留言斧正。如果不吝賜教小弟更加感謝。


參考資料

AFNetworking源碼 - Multipart協(xié)議,AFURLRequestSerialization和AFURLResponseSerialization
iOS開(kāi)發(fā)筆記之基于鍵值的觀察者模式(KVO)
AFNetworking 3.0 源碼解讀(三)之 AFURLRequestSerialization
HTTP請(qǐng)求報(bào)文(請(qǐng)求行、請(qǐng)求頭、請(qǐng)求體)
HTTP協(xié)議之multipart/form-data請(qǐng)求分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容