美文网首页技术干货IOS程序员
iOS源码补完计划--AFNetworking(四)

iOS源码补完计划--AFNetworking(四)

作者: kirito_song | 来源:发表于2018-05-23 17:43 被阅读84次

    目录

    • 前言
    • 流程图
    • 核心代码
    • 请求头
    • 请求体
      • 1、通过Parameters(参数字典)构建的请求体
        • 1、用户自定义block转译
        • 2、AFN默认的转义方式:
          • 将字典转化成字符串并且转译
          • 根据不同情况将参数字符串拼接到URL后、或者放入请求体
      • 2、通过文件构建的请求体
        • AFMultipartFormData协议
        • AFHTTPBodyPart
        • AFStreamingMultipartFormData
          • 核心方法(将文件分段拼接)
    • AFHTTPRequestSerializer
      • AFHTTPRequestSerializer.h
      • AFHTTPRequestSerializer.m
        • NSInputStream和NSOutputSteam的使用
    • APIDemo
    • 参考资料

    前言

    AFNetworking源码第四篇
    主要看了看AFURLRequestSerialization的内容
    负责网络请求NSMutableURLRequest对象的初始化
    以及请求头、请求体、参数、上传文件的自动化配置

    几千行代码、很长。但是读下来会受益匪浅

    AFN概述:《iOS源码补完计划--AFNetworking 3.1.0源码研读》

    流程图

    里面的文件实在是太多了、还是先弄个流程图好一些?


    AFHTTPRequestSerializer流程图

    解释图中几个相关的类:

    • AFHTTPRequestSerializer
      负责请求的生产
    • AFQueryStringPair
      内部有两个属性。分别代表字典的key和value。
    @property (readwrite, nonatomic, strong) id field;
    @property (readwrite, nonatomic, strong) id value;
    

    在将请求参数拼接进URL或请求体的时候会用到。

    • AFHTTPBodyPart
      请求体单个片段(也就是单个name)
    • AFStreamingMultipartFormData
      用来整合请求体信息、并且整合进request
    • AFMultipartFormData
      负责上传文件拼接的协议、AFStreamingMultipartFormData对象就遵从这个协议。内部将会先转化成AFHTTPBodyPart然后追加给AFStreamingMultipartFormData
      包括追加文件、二进制文件、数据流等等。

    核心代码

    还是先写一个普通的请求代码:

    AFHTTPRequestSerializer * requestSerializer =  [AFHTTPRequestSerializer serializer];
    [requestSerializer setValue:@"请求头value1" forHTTPHeaderField:@"请求头key1"];
    [requestSerializer setValue:@"请求头value2" forHTTPHeaderField:@"请求头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内部的操作很直观
    1、生成一个_MRequest
    2、如果你设置了一些通用属性、则覆盖一下。
    3、设置请求头请求体

    //通过请求方式、URL、参数字典生成请求
    - (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;
    
        //如果某个关键属性被自主设置过、则用新的。不然直接用模板生成的`NSMutableURLRequest`即可
        for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
            if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
                [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
            }
        }
    
        //对req进一步设置(拼接URL、请求体、请求头)
        mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
    
        return mutableRequest;
    }
    
    //监听集合。蜂窝网络、缓存策略、cookie、管线链接、网络服务类型、超时连接
    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];尤为重要、它完善了整个request的必要信息。

    - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                                   withParameters:(id)parameters
                                            error:(NSError *__autoreleasing *)error
    {
        NSParameterAssert(request);
    
        NSMutableURLRequest *mutableRequest = [request mutableCopy];
        //设置请求头
        [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
            if (![request valueForHTTPHeaderField:field]) {
                [mutableRequest setValue:value forHTTPHeaderField:field];
            }
        }];
    
        //根据参数parameters设置查询字段
        NSString *query = nil;
        if (parameters) {
            //看看参数是否需要用户自定义转译
            if (self.queryStringSerialization) {
                NSError *serializationError;
                //调用用户block、获得参数转译的字符串
                query = self.queryStringSerialization(request, parameters, &serializationError);
    
                if (serializationError) {
                    if (error) {
                        *error = serializationError;
                    }
    
                    return nil;
                }
            } else {
                //使用AFN的默认转译方式
                switch (self.queryStringSerializationStyle) {
                    case AFHTTPRequestQueryStringDefaultStyle:
                        query = AFQueryStringFromParameters(parameters);
                        break;
                }
            }
        }
    
        //如果请求方式是需要将查询参数拼接到URL后面的(默认包含`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 {
            //否则、则放入请求体
            // #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;
    }
    

    至此一个正常的请求就结束了。
    不过、既然是读源码、肯定还需要把每个代码都好好看看。


    请求头

    请求头可以通过外界设置、保存在字典里。在生成request的时候批量添加设置进去。

    /*
        Get方法
     */
    - (NSDictionary *)HTTPRequestHeaders {
        //将不可变字典转换成可变字典
        return [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
    }
    /*
        设置请求头
     */
    - (void)setValue:(NSString *)value
    forHTTPHeaderField:(NSString *)field
    {
        //对请求头字典进行追加
        [self.mutableHTTPRequestHeaders setValue:value forKey:field];
    }
    
    - (NSString *)valueForHTTPHeaderField:(NSString *)field {
        //根据不同的key提取出value
        return [self.mutableHTTPRequestHeaders valueForKey:field];
    }
    
    //通过账号密码设置授权请求头
    - (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
                                           password:(NSString *)password
    {
        //转化成data
        NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding];
        //base64编码加密
        NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
        //加入请求头字典
        [self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"];
    }
    //清除授权用请求头
    - (void)clearAuthorizationHeader {
        [self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"];
    }
    

    针对AFN而不是multipart 协议来看、可以讲的没有多少。


    请求体

    • 1、通过Parameters(参数字典)构建的请求体

    既然提到Parameters、那么就像上面代码里写的一样、所有的Parameters字典、都需要被转义成字符串。

    AFN为我们提供了两种转换的方式。

    • 1、用户自定义block转译

    没啥说的、参数都给你。自己转译完还给AFN字符串就行

    - (void)setQueryStringSerializationWithBlock:(NSString *(^)(NSURLRequest *, id, NSError *__autoreleasing *))block {
        self.queryStringSerialization = block;
    }
    
    • 2、AFN默认的转义方式:
    switch (self.queryStringSerializationStyle) {
        case AFHTTPRequestQueryStringDefaultStyle:
            query = AFQueryStringFromParameters(parameters);
            break;
    }
    

    虽然是个switch、但是queryStringSerializationStyle默认目前只被提供了一种:

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

    但是写成枚举、是为了将来更好的扩展和维护。像这种有可能扩展的地方、是我们值得借鉴的。

    • 将字典转化成字符串并且转译
    //将字典参数转化成字符串
    NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
        NSMutableArray *mutablePairs = [NSMutableArray array];
        for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
            //将AFQueryStringPair对象转化成key=value的格式并且传递给新的数组
            [mutablePairs addObject:[pair URLEncodedStringValue]];
        }
        //开头添加"&"
        return [mutablePairs componentsJoinedByString:@"&"];
    }
    
    //将字典转化成数组{key1[key2]value}
    NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
        return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
    }
    
    //把key && value 转成数组
    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]]) {
            //参数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 ]
                对所有的字典里的nestedKey进行排序
             */
            for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
                //获取nestedKey里的value
                id nestedValue = dictionary[nestedKey];
                if (nestedValue) {
                    if (key) {
                        //如果指明了key、则为二级字典。用key[nestedKey]进行遍历
                        //比如@{aaa:@{bbb:ccc}};就会被变成aaa[bbb]作为key
                        [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[%@]", key, nestedKey], nestedValue)];
                    }else {
                        //如果没有传入nestedKey、则直接为一级字典。用subkey进行遍历
                        [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组合
            //如果是字典的话就会出现key = @"aaaa[bbbb]" value = @"cccc"这种情况、后面会再转化
            [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
        }
    
        //返回最终的`AFQueryStringPair`对象数组
        return mutableQueryStringComponents;
    }
    
    • 字典转字符串
      NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value)
      利用递归的方式对深层次的参数(value是NSDictionary/NSArray/NSSet)进行拆分。
    • AFQueryStringPair对象
    @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
    

    用来储存每个field(key)对应的value。
    比如@{aaa:@{bbb:ccc}};就会被变成aaa[bbb]作为field

    • 转译方法
      将每个AFQueryStringPair转译成字符串[pair URLEncodedStringValue]

    如果有value则使用key=value、否则直接转译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 规范下需要被保留的字符
    ":", "#", "[", "]", "@", "?", "/"
    "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
    在RFC 3986 规范 Section 3.4 下 的查询字段、除了"?","/"以外的特殊字符都应该被转义。所以有了以下这个方法:

    /**
     对字符串编码
     */
    NSString * AFPercentEscapedStringFromString(NSString *string) {
        //":", "#", "[", "]", "@", "?", "/"  除去"?","/"
        static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
        
        //"!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
        static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
    
        
        //将以上两种设置为排除
        NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
        /*
            只是[NSCharacterSet URLQueryAllowedCharacterSet]的话以上字符默认是不转译的
            需要移出去。只保留"?"和"/"不转译
        */
        [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
    
        // FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
        // return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
    
        //每次最多转译50个字符
        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;
    }
    
    • 根据不同情况将参数字符串拼接到URL后、或者放入请求体
    //如果请求方式是需要将查询参数拼接到URL后面的(默认包含`GET``HEAD``DELETE`)、则拼接
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query && query.length > 0) {
            //原url带有参数、则拼接'&'。没参数则拼接'?'
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
        }
    } else {
        //否则、则放入请求体
        // #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这个属性。
    用户可以自定义追加或者删除这个合集的内容。

    /**
        需要拼接参数的请求方法、默认包含`GET``HEAD``DELETE`
     */
    @property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;
    
    • 2、通过文件构建的请求体
    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协议
      文件的方式主要通过协议AFMultipartFormData进行、有很多添加方式。
    @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
    

    在进行必要的Content-Type生成后、都会流入以下方法:

    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实例、追加进self.bodyStream(AFStreamingMultipartFormData实例)

    • AFHTTPBodyPart

    单个请求体文件

    @interface AFHTTPBodyPart : NSObject
    @property (nonatomic, assign) NSStringEncoding stringEncoding;//编码
    @property (nonatomic, strong) NSDictionary *headers;//头信息
    @property (nonatomic, copy) NSString *boundary;//边界
    @property (nonatomic, strong) id body;//内容
    @property (nonatomic, assign) unsigned long long bodyContentLength;//内容大小
    @property (nonatomic, strong) NSInputStream *inputStream;//流
    
    @property (nonatomic, assign) BOOL hasInitialBoundary;//是否有初始边界
    @property (nonatomic, assign) BOOL hasFinalBoundary;//是否有结束边界
    
    @property (readonly, nonatomic, assign, getter = hasBytesAvailable) BOOL bytesAvailable;//body是否存在
    @property (readonly, nonatomic, assign) unsigned long long contentLength;//长度
    
    //读取数据
    - (NSInteger)read:(uint8_t *)buffer
            maxLength:(NSUInteger)length;
    @end
    
    • AFStreamingMultipartFormData
    @interface AFStreamingMultipartFormData ()
    @property (readwrite, nonatomic, copy) NSMutableURLRequest *request;//请求request
    @property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;//编码
    @property (readwrite, nonatomic, copy) NSString *boundary;//边界
    @property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream;//body文件整合工具
    @end
    

    继承自NSInputStream、因为最后需要将整个实例接入给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];
        //将请求体接入request
        [self.request setHTTPBodyStream:self.bodyStream];
        //multipart协议请求头
        [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;
    }
    

    我们注意到有两个请求头的设置、是multipart协议独有的。
    其中、self.bodyStream是我们正在说的AFStreamingMultipartFormData实例。
    self.boundary是边界分隔符。
    AFStreamingMultipartFormData初始化时一并初始完成。

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

    初次之外、还有三个函数

    //如果是开头分隔符的,那么只需在分隔符结尾加一个换行符
    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;
        }
    }
    

    服务于第二个请求头[self.bodyStream contentLength]

    //计算body大小
    - (unsigned long long)contentLength {
    
        unsigned long long length = 0;
        
        //初始边界  是否有初始边界?初始边界:封装边界(比初始边界多了个开头的\r\n)
        NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding];
        length += [encapsulationBoundaryData length];
    
        //头长度
        NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding];
        length += [headersData length];
    
        //内容长度
        length += _bodyContentLength;
    
        //结束边界 是否有结束边界?结束边界:0
        NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]);
        length += [closingBoundaryData length];
    
        return length;
    }
    

    初始化、设置边界、追加body文件、验证有效性

    - (instancetype)initWithStringEncoding:(NSStringEncoding)encoding {
        self = [super init];
        if (!self) {
            return nil;
        }
    
        self.stringEncoding = encoding;
        self.HTTPBodyParts = [NSMutableArray array];
        self.numberOfBytesInPacket = NSIntegerMax;
    
        return self;
    }
    
    //设置初始边界和结束边界
    - (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;
    }
    
    • 核心方法(将文件分段拼接)

    之所以是核心方法、因为这是AFN的一个亮点。

    平时在用NSURLRequest上传文件时,一般是两种方法:
    1、一个是设置body、但是如果文件稍大的话,将会撑爆内存。
    2、另外一种则是创建一个临时文件、将数据拼接进去、然后将文件路径设置为bodyStream、这样就可以分片的上传了。
    

    而AFN这种方式无疑更好、具体来看:

    - (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"
        //遍历读取数据
        while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
            
            //body不存在或者没有可读字节
            if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
                //把下一个body文件赋值给当前body
                if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
                    break;
                }
            } else {
                //存在可读数据
                
                NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
                
                //把当前body读取到buffer中。内部会采用递归的方式将数据分段写入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

    其实到这已经没什么特别难得东西了、只剩下一些流程任务

    • AFHTTPRequestSerializer.h
    @interface AFHTTPRequestSerializer : NSObject <AFURLRequestSerialization>
    
    /**
        编码方式。默认`NSUTF8StringEncoding`
     */
    @property (nonatomic, assign) NSStringEncoding stringEncoding;
    
    /**
        允许蜂窝网络
     */
    @property (nonatomic, assign) BOOL allowsCellularAccess;
    
    /**
        请求的缓存策略
     
         typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
         {
         //默认策略。缓存存在则根据request中Cache-Control字段进行不同操作、否则则请求
         NSURLRequestUseProtocolCachePolicy = 0,
         //每次都请求服务器
         NSURLRequestReloadIgnoringLocalCacheData = 1,
         //忽略本地缓存和中间代理。直接请求原服务器 -- 未实现
         NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented
         //同NSURLRequestReloadIgnoringLocalCacheData
         NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
         //有缓存就是用、不管其有效性。即忽略Cache-Control字段、没有就访问源server
         NSURLRequestReturnCacheDataElseLoad = 2,
         //只加载本地数据,不做其他操作,适用于没有网路的情况
         NSURLRequestReturnCacheDataDontLoad = 3,
         //缓存数据必须得到服务器确认才能使用--未实现
         NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented
     };
    
     */
    @property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
    
    /**
        是否使用默认的cookie处理、默认YES
     */
    @property (nonatomic, assign) BOOL HTTPShouldHandleCookies;
    
    /**
        管线化传输、默认NO
        正常的HTTP请求都是一个请求对应一个连接、每次TCP链接需要一定的时间。
        管线化:允许一次发送一组请求而不必等到响应。但并不是所有服务器都支持、需要和服务器协商。并且需要提前建立好链接才能使用
     */
    @property (nonatomic, assign) BOOL HTTPShouldUsePipelining;
    
    /**
        网络服务类型
         typedef NS_ENUM(NSUInteger, NSURLRequestNetworkServiceType)
         {
         NSURLNetworkServiceTypeDefault = 0, // 普通网络传输,默认使用这个
         NSURLNetworkServiceTypeVoIP = 1,    // 网络语音通信传输,只能在VoIP使用
         NSURLNetworkServiceTypeVideo = 2,   // 影像传输
         NSURLNetworkServiceTypeBackground = 3, // 网络后台传输,优先级不高时可使用。对用户不需要的网络操作可使用
         NSURLNetworkServiceTypeVoice = 4       // 语音传输
         };
     */
    @property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;
    
    /**
         超时时间
     */
    @property (nonatomic, assign) NSTimeInterval timeoutInterval;
    
    ///---------------------------------------
    /// @name Configuring HTTP Request Headers
    ///---------------------------------------
    
    /**
         请求头
     */
    @property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;
    
    /**
        初始化一个默认配置的req
     */
    + (instancetype)serializer;
    
    /**
        设置httpheader、如果value为nil、则移除field(key)
     */
    - (void)setValue:(nullable NSString *)value
    forHTTPHeaderField:(NSString *)field;
    
    /**
        返回field(key)标记的内容
     */
    - (nullable NSString *)valueForHTTPHeaderField:(NSString *)field;
    
    /**
        设置请求头中的`Authorization`字段
     */
    - (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
                                           password:(NSString *)password;
    
    /**
        清空请求头
     */
    - (void)clearAuthorizationHeader;
    
    ///-------------------------------------------------------
    /// @name 请求参数配置
    ///-------------------------------------------------------
    
    /**
        需要拼接参数的请求方法、默认包含`GET``HEAD``DELETE`
     */
    @property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;
    
    /**
        查询参数的转义样式.(目前只有一种)
     */
    - (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style;
    
    /**
        你也可以自定义参数的转义方式
     */
    - (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;
    
    ///-------------------------------
    ///   核心方法
    ///-------------------------------
    
    /**
        如果请求方式为GET`, `HEAD`, or `DELETE时、参数会被拼接到URL中、否则当做body处理
     */
    - (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                     URLString:(NSString *)URLString
                                    parameters:(nullable id)parameters
                                         error:(NSError * _Nullable __autoreleasing *)error;
    
    /**
        支持上传数据
     */
    - (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;
    
    /**
        这个方法可以把一个带有bodystream请求体的req、转化成不含bodystream的req。
        其中的文件会被转写到fileURL的路径下。
        你可以通过upload直接上传
        NSURLSessionTask中有一个bug,当HTTP body的内容是来自NSStream的时候,request无法发送Content-Length到服务器端,此问题在Amazon S3的Web服务中尤为显著。
     */
    - (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;//记录修改过的属性
    @property (readwrite, nonatomic, strong) NSMutableDictionary *mutableHTTPRequestHeaders;//请求头字典
    @property (readwrite, nonatomic, assign) AFHTTPRequestQueryStringSerializationStyle queryStringSerializationStyle;//参数转义的样式
    @property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization;//自定义参数转义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
        /**
         *  传递可接受的语言,q代表对语言的喜好程度,默认是取出前5个的数据,不足5个,取实际的个数
         */
        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;
        }];
        //放到请求头里
        [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;
                }
            }
            //将用户信息写入请求头
            [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];
        //批量添加监听
        for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
            if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
                [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
            }
        }
    
        return self;
    }
    
    - (void)dealloc {
        //移除监听
        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 -
    
    /*
        对请求头进行操作
     */
    - (NSDictionary *)HTTPRequestHeaders {
        //将不可变字典转换成可变字典
        return [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
    }
    
    - (void)setValue:(NSString *)value
    forHTTPHeaderField:(NSString *)field
    {
        //对请求头字典进行追加
        [self.mutableHTTPRequestHeaders setValue:value forKey:field];
    }
    
    - (NSString *)valueForHTTPHeaderField:(NSString *)field {
        //根据不同的key提取出value
        return [self.mutableHTTPRequestHeaders valueForKey:field];
    }
    
    //通过账号密码设置授权请求头
    - (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
                                           password:(NSString *)password
    {
        //转化成data
        NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding];
        //base64编码加密
        NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
        //加入请求头字典
        [self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"];
    }
    
    //清除授权用请求头
    - (void)clearAuthorizationHeader {
        [self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"];
    }
    
    #pragma mark -
    //设置查询参数的编码方式
    - (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style {
        self.queryStringSerializationStyle = style;
        self.queryStringSerialization = nil;
    }
    
    //设置查询参数自定义编码的block
    - (void)setQueryStringSerializationWithBlock:(NSString *(^)(NSURLRequest *, id, NSError *__autoreleasing *))block {
        self.queryStringSerialization = block;
    }
    
    #pragma mark -
    
    //通过请求方式、URL、参数字典生成请求
    - (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;
    
        //如果某个关键属性被自主设置过、则用新的。不然直接用模板生成的`NSMutableURLRequest`即可
        for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
            if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
                [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
            }
        }
    
        //对req进一步设置(拼接URL、请求体、请求头)
        mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
    
        return mutableRequest;
    }
    
    /*
        数据上传专用处理
        不允许GET&&HEAD方法
        参数将会拼接到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];
    
        // 创建一个`AFStreamingMultipartFormData`实例,用来处理数据。
        __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
    
        if (parameters) {
            //遍历参数字典(转化成的数组)
            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) {
                    //将每个参数拼接到`AFStreamingMultipartFormData`对象中
                    [formData appendPartWithFormData:data name:[pair.field description]];
                }
            }
        }
    
        if (block) {
            block(formData);
        }
        //将请求体数据接入请求、并且返回处理完成的req
        return [formData requestByFinalizingMultipartFormData];
    }
    
    //将请求体中的文件、剥离出来
    - (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
                                 writingStreamContentsToFile:(NSURL *)fileURL
                                           completionHandler:(void (^)(NSError *error))handler
    {
        //是否有HTTPBodyStream
        NSParameterAssert(request.HTTPBodyStream);
        //是否是个文件
        NSParameterAssert([fileURL isFileURL]);
    
        //获取写入对象
        NSInputStream *inputStream = request.HTTPBodyStream;
       // 使用outputStream将HTTPBodyStream的内容写入到路径为fileURL的文件中
        NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO];
        __block NSError *error = nil;
    
        //异步写入
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            //开启读写工具
            [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    
            [inputStream open];
            [outputStream open];
    
            
            //读取数据 `hasBytesAvailable`代表是否有可用字节
            while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) {
                uint8_t buffer[1024];
                //将数据读取到buffer、每次最大读取1024
                NSInteger bytesRead = [inputStream read:buffer maxLength:1024];
                if (inputStream.streamError || bytesRead < 0) {
                    error = inputStream.streamError;
                    break;
                }
                
                //将buffer写入
                NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead];
                if (outputStream.streamError || bytesWritten < 0) {
                    error = outputStream.streamError;
                    break;
                }
                
                //如果读取与写入都为0、代表转写结束
                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];
        //设置请求头
        [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
            if (![request valueForHTTPHeaderField:field]) {
                [mutableRequest setValue:value forHTTPHeaderField:field];
            }
        }];
    
        //根据参数parameters设置查询字段
        NSString *query = nil;
        if (parameters) {
            //看看参数是否需要用户自定义转译
            if (self.queryStringSerialization) {
                NSError *serializationError;
                //调用用户block、获得参数转译的字符串
                query = self.queryStringSerialization(request, parameters, &serializationError);
    
                if (serializationError) {
                    if (error) {
                        *error = serializationError;
                    }
    
                    return nil;
                }
            } else {
                //使用AFN的默认转译方式
                switch (self.queryStringSerializationStyle) {
                    case AFHTTPRequestQueryStringDefaultStyle:
                        query = AFQueryStringFromParameters(parameters);
                        break;
                }
            }
        }
    
        //如果请求方式是需要将查询参数拼接到URL后面的(默认包含`GET``HEAD``DELETE`)、则拼接
        if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
            if (query && query.length > 0) {
                //原url带有参数、则拼接'&'。没参数则拼接'?'
                mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
            }
        } else {
            //否则、则放入请求体
            // #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
    //可以决定是否发送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]]) {
                //如果没有新值、则清空所属监听
                [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 - 请求体
    
    //请求体拼接
    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;
    
    
    /**
        单个请求体文件
     */
    @interface AFHTTPBodyPart : NSObject
    @property (nonatomic, assign) NSStringEncoding stringEncoding;//编码
    @property (nonatomic, strong) NSDictionary *headers;//请求头
    @property (nonatomic, copy) NSString *boundary;//边界
    @property (nonatomic, strong) id body;//内容
    @property (nonatomic, assign) unsigned long long bodyContentLength;//内容大小
    @property (nonatomic, strong) NSInputStream *inputStream;//流
    
    @property (nonatomic, assign) BOOL hasInitialBoundary;//是否有初始边界
    @property (nonatomic, assign) BOOL hasFinalBoundary;//是否有结束边界
    
    @property (readonly, nonatomic, assign, getter = hasBytesAvailable) BOOL bytesAvailable;//body是否存在
    @property (readonly, nonatomic, assign) unsigned long long contentLength;//长度
    
    //读取数据
    - (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;//内容大小
    @property (readonly, nonatomic, assign, getter = isEmpty) BOOL empty;//是否为空
    
    //初始化以及编码方式
    - (instancetype)initWithStringEncoding:(NSStringEncoding)encoding;
    //设置请求体数据片段的初始边界和结束边界
    - (void)setInitialAndFinalBoundaries;
    //追加请求体数据
    - (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart;
    @end
    
    #pragma mark -
    
    //数据和request连接器
    @interface AFStreamingMultipartFormData ()
    @property (readwrite, nonatomic, copy) NSMutableURLRequest *request;//请求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     结果就是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 - 将文件添加进请求体
    - (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];
        //请求头
        [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
        [mutableHeaders setValue:mimeType forKey:@"Content-Type"];
    
        //创建请求体模型、加入bodyStream准备处理
        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 - 将数据流添加进请求体
    - (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 - 通过二进制文件设置请求体
    - (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 - 将请求体数据接入请求
    - (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];
        //将请求体接入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的这个方法可以将请求体中的文件、剥离出来。具体怎么剥离呢?
    就是将NSInputStream对象的内容转写到NSOutputSteam
    和之前AFStreamingMultipartFormData的转写方法相比、少了很多判断、是最干净的案例了。

    - (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
                                 writingStreamContentsToFile:(NSURL *)fileURL
                                           completionHandler:(void (^)(NSError *error))handler
    {
        //是否有HTTPBodyStream
        NSParameterAssert(request.HTTPBodyStream);
        //是否是个文件
        NSParameterAssert([fileURL isFileURL]);
    
        //获取写入对象
        NSInputStream *inputStream = request.HTTPBodyStream;
       // 使用outputStream将HTTPBodyStream的内容写入到路径为fileURL的文件中
        NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO];
        __block NSError *error = nil;
    
        //异步写入
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            //开启读写工具
            [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    
            [inputStream open];
            [outputStream open];
    
            
            //读取数据 `hasBytesAvailable`代表是否有可用字节
            while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) {
                uint8_t buffer[1024];
                //将数据读取到buffer、每次最大读取1024
                NSInteger bytesRead = [inputStream read:buffer maxLength:1024];
                if (inputStream.streamError || bytesRead < 0) {
                    error = inputStream.streamError;
                    break;
                }
                
                //将buffer写入
                NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead];
                if (outputStream.streamError || bytesWritten < 0) {
                    error = outputStream.streamError;
                    break;
                }
                
                //如果读取与写入都为0、代表转写结束
                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;
    }
    

    当然、这个HttpBodySteam必须是通过AFN生成的AFMultipartBodyStream实例
    才会执行这个代理方法

    - (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"
        //遍历读取数据
        while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
            
            //body不存在或者没有可读字节
            if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
                //把下一个body文件赋值给当前body
                if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
                    break;
                }
            } else {
                //存在可读数据
                
                NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
                
                //把当前body读取到buffer中。内部会采用递归的方式将数据分段写入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

    说实话两个文件加起来两千多行、每行都想顾及到我自己都不知道怎么写...但是把很多代码都进行了注释、有兴趣可以自取:

    GitHub


    参考资料

    AFNetworking源码 - Multipart协议,AFURLRequestSerialization和AFURLResponseSerialization
    iOS开发笔记之基于键值的观察者模式(KVO)
    AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization
    HTTP请求报文(请求行、请求头、请求体)
    HTTP协议之multipart/form-data请求分析

    相关文章

      网友评论

        本文标题:iOS源码补完计划--AFNetworking(四)

        本文链接:https://www.haomeiwen.com/subject/gywhjftx.html