AFNetworking知识点之AFURLRequestSerialization

AFURLRequestSerialization功能是在创建网络请求的时候帮我们创建一个NSURLRequest,并封装请求参数到NSURLRequest中。
让我们一起学习一下创建一个好的NSURLRequest都需要做些什么吧。

学习AFURLRequestSerialization之前,我们要准备好两个知识点:multipart/form-data请求url编码
multipart/form-data请求是这篇内容的重点,需要我们明白multipart/form-data请求和一般的请求的不同点在哪里,准备一下这方面的知识,我前面一篇文章已经讲过了,需要的可以看一下。
Url编码这里简单介绍一下,够我们用就行了。URI是统一资源标识的意思,通常我们所说的Url只是URI的一种。下面提到的Url编码,实际上应该指是URI编码。
为什么需要Url编码
因为Url中有些字符会引起歧义。例如Url参数字符串中使用key=value键值对这样的形式来传参,键值对之间以&符号分隔,如http://localhost:8080/a?q=abc&ie=utf-8,如果你的value字符串中包含了?、=或者&,那么势必会造成接收Url的服务器解析错误,因此必须将引起歧义的&和= 符号进行转义,也就是对其进行编码。
哪些会引起歧义
RFC3986文档规定,Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符。
RFC3986文档还规定了保留字符和不安全字符。
保留字符:
通用定界符:":", "#", "[", "]", "@", "?", "/";
子分隔符: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=";
不安全字符:
" ", """, "<", ">", "#", "%", "{", "}", "|", "", "^", "[", "]", "`", "~";
因此在你的url如果包含不安全字符,那么就需要进行编码,而如果你的参数中包含上述这些字符(保留字符和不安全字符)都需要进行编码。但是在In RFC 3986 - Section 3.4中规定"?"、"/"可以不需要编码。
怎么编码

! * " ' ( ) ; : @ &
%21 %2A %22 %27 %28 %29 %3B %3A %40 %26
= + $ , / ? % # [ ]
%3D %2B %24 %2C %2F %3F %25 %23 %5B %5D

对于非ASCII字符,需要使用ASCII字符集的超集进行编码得到相应的字节,然后对每个字节执行百分号编码。 对于Unicode字符,RFC文档建议使用utf-8对其进行编码得到相应的字节,然后对每个字节执行百分号编码。如“中文”使用UTF-8字符集得到 的字节为0xE4 0xB8 0xAD 0xE6 0x96 0x87,经过Url编码之后得到“%E4%B8%AD%E6%96%87”。

如果某个字节对应着ASCII字符集中的某个非保留字符,则此字节无需使用百分号表示。 例如“Url编码”,使用UTF-8编码得到的字节是0x55 0x72 0x6C 0xE7 0xBC 0x96 0xE7 0xA0 0x81,由于前三个字节对应着ASCII中的非保留字符“Url”,因此这三个字节可以用非保留字符“Url”表示。最终的Url编码可以简化成 “Url%E7%BC%96%E7%A0%81” ,当然,如果你用"%55%72%6C%E7%BC%96%E7%A0%81”也是可以的。

准备知识到这儿就差不多了,下面进入我们这章的正题:AFURLRequestSerialization

/**
 把请求参数按照前面讲的百分号编码方式进行编码
 */
FOUNDATION_EXPORT NSString * AFPercentEscapedStringFromString(NSString *string);

/**
 请求参数序列化方法,会先把参数用用上面的方法编码,然后把这些参数照着key=value&key=value的方式设置。
 */
FOUNDATION_EXPORT NSString * AFQueryStringFromParameters(NSDictionary *parameters);

上面是两个序列化方面的函数,接下来就是两个协议

/**
 这个协议主要是request对象用来设置请求头、请求体、查询字符串的时候使用
 例如,一个JSON的request要将HTTP主体设置为JSON表示,并将HTTP的“Content-Type”头域设置为“application/ JSON”。
 */
@protocol AFURLRequestSerialization 

/**
 这个实现这个协议目的的方法
 */
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(nullable id)parameters
                                        error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

@end
/**
这个协议是在multipart/form-data请求中调用的,一般的get和post请求用不到。
主要是用于外部调用AFHTTPRequestSerializer 中multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:方法获取request的时候,对constructingBodyWithBlock参数的支持。
实际上是AFStreamingMultipartFormData遵守了这个协议,所以就实现了下面这些方法,外部就可以通过constructingBodyWithBlock的参数的AFStreamingMultipartFormData对象调用下面这些方法。
 */
@protocol AFMultipartFormData

/**
 根据文件路径和请求参数名来添加请求参数,文件名和MIME类型根据文件路径获取,出现错误就在error里描述。
设置请求头` Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`,后面都一样,就不写了。
 */
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                         name:(NSString *)name
                        error:(NSError * _Nullable __autoreleasing *)error;

/**
 根据文件路径、参数名、文件名、mimeType来添加请求参数
 */
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                         name:(NSString *)name
                     fileName:(NSString *)fileName
                     mimeType:(NSString *)mimeType
                        error:(NSError * _Nullable __autoreleasing *)error;

/**
 根据传入的流文件、参数名、文件名、长度、mimeType等参数来设置request请求参数。
 */
- (void)appendPartWithInputStream:(nullable NSInputStream *)inputStream
                             name:(NSString *)name
                         fileName:(NSString *)fileName
                           length:(int64_t)length
                         mimeType:(NSString *)mimeType;

/**
根据传入的data数据、参数名、文件名、mimeType等参数来设置request请求参数。
 */
- (void)appendPartWithFileData:(NSData *)data
                          name:(NSString *)name
                      fileName:(NSString *)fileName
                      mimeType:(NSString *)mimeType;

/**
 根据传入的data数据、参数名等参数来设置request请求参数。
不设置filename和content-type,是这样的:`Content-Disposition: form-data; name=#{name}"`
 */

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


/**
 添加自定义的头文件和已经编码过的request body来设置请求参数
 */
- (void)appendPartWithHeaders:(nullable NSDictionary  *)headers
                         body:(NSData *)body;

/**
 限制数据包的大小和设置一个延时读取。
是为了防止你数据包内容过大,超过了请求体正文的大小,而导致失败。
 */
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
                                  delay:(NSTimeInterval)delay;

@end

这一块就是针对multipart/form-data请求时,添加文件、流、data等参数时调用的。AFStreamingMultipartFormData类会遵守这个协议,然后把AFStreamingMultipartFormData对象作为获取请求方法(例如:multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:)的参数,然后AFStreamingMultipartFormData对象参数会把添加的参数组装成AFHTTPBodyPart对象添加到AFMultipartBodyStream对象中,然后把AFMultipartBodyStream对象设置为request的bodyStream。
所以,实质上实现了AFMultipartFormData协议的AFStreamingMultipartFormData对象是作为中间人,把参数添加到AFMultipartBodyStream对象中。

接着看AFHTTPRequestSerializer,这就是对NSMutableURLRequest的一些基本设置和获取NSMutableURLRequest的方法。

@interface AFHTTPRequestSerializer : NSObject 

/**
 编码方式
 */
@property (nonatomic, assign) NSStringEncoding stringEncoding;

 /**
 是否允许使用设备的蜂窝移动网络来创建request,默认为YES
 */
@property (nonatomic, assign) BOOL allowsCellularAccess;

/**
 创建的request所使用的缓存策略,默认使用`NSURLRequestUseProtocolCachePolicy`,该策略表示 
 如果缓存不存在,直接从服务端获取。如果缓存存在,会根据response中的Cache-Control字段判断
 下一步操作,如: Cache-Control字段为must-revalidata, 则 询问服务端该数据是否有更新,无更新话
 直接返回给用户缓存数据,若已更新,则请求服务端.
 */
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;

/**
 HTTPShouldHandleCookies表示是否应该给request设置cookie并随request一起发送出去,默认是YES
 */
@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;

/**
 HTTPShouldUsePipelining表示receiver(理解为iOS客户端)的下一个信息是否必须等到上一个请求回复才能发送,默认是NO。
 */
@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;

/**
 设定request的network service类型. 默认是`NSURLNetworkServiceTypeDefault`.
 这个network service是为了告诉系统网络层这个request使用的目的
 比如NSURLNetworkServiceTypeVoIP表示的就这个request是用来请求网际协议通话技术(Voice over IP)。
 系统能根据提供的信息来优化网络处理,从而优化电池寿命,网络性能等等
 */
@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;

/**
 超时机制,默认60秒
 */
@property (nonatomic, assign) NSTimeInterval timeoutInterval;

///---------------------------------------
/// 请求头相关配置
///---------------------------------------
/**
 获取http请求头的属性,默认情况下包含以下字段:
- `Accept-Language` 接收语言
- `User-Agent` 带有各种bundle标识符和操作系统名称的内容
只读属性,如果需要添加或者删除需要调用`setValue:
forHTTPHeaderField: `.
 */
@property (readonly, nonatomic, strong) NSDictionary  *HTTPRequestHeaders;

/**
 返回一个默认配置的序列化对象
 */
+ (instancetype)serializer;

/**
 设置http请求头,如果value是nil,就删除已经存在的字段。
 */
- (void)setValue:(nullable NSString *)value
forHTTPHeaderField:(NSString *)field;

/**
 获取http请求头对应的头域的值。
 */
- (nullable NSString *)valueForHTTPHeaderField:(NSString *)field;

/**
 对用户名和密码用":"拼接,然后进行base 64编码,设置Basic Authorization头域
 */
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
                                       password:(NSString *)password;

/**
 清空Authorization的请求头内容
 */
- (void)clearAuthorizationHeader;

///-------------------------------------------------------
/// query字符串参数序列化的配置
///-------------------------------------------------------

/**
 需要把参数拼接到url后面的方法集合,默认包括 `GET`, `HEAD`和 `DELETE` 
 */
@property (nonatomic, strong) NSSet  *HTTPMethodsEncodingParametersInURI;

/**
 根据预定义的方式设置序列化的方法
 */
- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style;

/**
 设置一个自定义的序列化方法block,在序列化request的时候如果有自定义的方法,会优先调用,否则才调用默认的。
定义将参数编码到查询字符串过程中的块。此块返回查询字符串并接受三个参数:请求、要编码的参数,以及在试图为给定请求编码参数时发生的错误。
 */
- (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;

///-------------------------------
/// 创建一个request对象
///-------------------------------

/**
根据指定的HTTP方法和URL字符串创建一个`NSMutableUrlRequest `对象。
如果HTTP方法是“get”、“head”或“delete”,则这些参数将用于构造一个URL编码的query字符串,该字符串被追加到请求的URL中。
否则,根据 `parameterencoding `属性的值进行编码,并设置为请求体。
 */
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(nullable id)parameters
                                     error:(NSError * _Nullable __autoreleasing *)error;

/**
 通过构建`multipart/form-data`请求体来创建NSMutableURLRequest
 @param block 一个带有实现了协议的对象的block,用于向`multipart/form-data`类型的请求体中添加数据。
上面讲AFMultipartFormData协议的时候就讲过了,实现了< AFMultipartFormData >协议的AFStreamingMultipartFormData类作为中间人,
把参数组装为AFHTTPBodyPart类,然后把AFHTTPBodyPart类的实体对象加到AFMultipartBodyStream中。AFMultipartBodyStream就是http请求体。
 */
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
                                              URLString:(NSString *)URLString
                                             parameters:(nullable NSDictionary  *)parameters
                              constructingBodyWithBlock:(nullable void (^)(id  formData))block
                                                  error:(NSError * _Nullable __autoreleasing *)error;

/**
这个方法会把request的HTTPBodyStream写如果指定文件,然后返回一个删除HTTPBodyStream之后的request。
 这样做为了修复一个bug--在与Amazon S3服务器交互时无法发送Content-Length头域.
被写入HTTPBodyStream的文件将会被`AFURLSessionManager`的`uploadTaskWithRequest:fromFile:progress:completionHandler:`发送或者以data的形式作为HTTPBody被发送。
 */
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
                             writingStreamContentsToFile:(NSURL *)fileURL
                                       completionHandler:(nullable void (^)(NSError * _Nullable error))handler;

@end

加下来就是正文了,看看前面这些方法都是怎么实现的,用到了哪些知识点。

NSString * AFPercentEscapedStringFromString(NSString *string) {
    //需要做百分号编码处理的字符串,前面讲过的
    static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // RFC 3986文档允许"?"、"/"可以不用编码
    static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";

    //获取系统中不需要百分号编码的字符集合
    NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
    
    //删除RFC 3986文档规定要做编码的字符,剩下的就是最终不需要做编码的字符集合
    [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];

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

    static NSUInteger const batchSize = 50;

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

    while (index < string.length) {
        NSUInteger length = MIN(string.length - index, batchSize);
        NSRange range = NSMakeRange(index, length);

        //  获取真正的range,防止特殊字符被分开编码
        range = [string rangeOfComposedCharacterSequencesForRange:range];

        NSString *substring = [string substringWithRange:range];
        //对除去allowedCharacterSet字符集合之外的字符进行编码
        NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
        [escaped appendString:encoded];

        index += range.length;
    }

    return escaped;
}

大部分知识点注释都解释清楚了啊,有一个rangeOfComposedCharacterSequencesForRange :不是很清楚啊,可以看一下我之前的关于字符串真正的range的讲解。

@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;

- (instancetype)initWithField:(id)field value:(id)value;

- (NSString *)URLEncodedStringValue;
@end

@implementation AFQueryStringPair

- (instancetype)initWithField:(id)field value:(id)value {
    self = [super init];
    if (!self) {
        return nil;
    }

    self.field = field;
    self.value = value;

    return self;
}

- (NSString *)URLEncodedStringValue {
    if (!self.value || [self.value isEqual:[NSNull null]]) {
        return AFPercentEscapedStringFromString([self.field description]);
    } else {
        return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
    }
}

@end

这个内部类主要是为了方便把键值对进行百分号编码之后用"="连接起来,很简单,也没什么知识点,自己看看就好。

//把参数编码之后编码为用&符连接起来
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }
    //把数组中的元素用&链接起来
    return [mutablePairs componentsJoinedByString:@"&"];
}

//把字典转化为AFQueryStringPair对象数组
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
    return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
//把key-value键值对转化为AFQueryStringPair对象数组
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]]) {
        NSDictionary *dictionary = value;
        // 按照sortDescriptor排序所有的key,在反序列化有歧义的序列时时比较重要的
        for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            id nestedValue = dictionary[nestedKey];
            if (nestedValue) {
                [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
            }
        }
    } else if ([value isKindOfClass:[NSArray class]]) {
        NSArray *array = value;
        for (id nestedValue in array) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
        }
    } else if ([value isKindOfClass:[NSSet class]]) {
        NSSet *set = value;
        for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
        }
    } else {
        [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
    }

    return mutableQueryStringComponents;
}

都很简单的知识点,这几个函数是用于普通的get或者set方法的参数的序列化,AFQueryStringPairsFromKeyAndValue函数把参数转化为AFQueryStringPair对象的数组,然后AFQueryStringFromParameters函数就调用间接AFQueryStringPairsFromKeyAndValue函数获取AFQueryStringPair对象数组,把每个对象进行url编码之后,用&符连接起来生成一个字符串就完成了。

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

这个就是指定了request请求序列化要观察的属性列表、是一个数组,里面有对蜂窝数据、缓存策略、cookie、管道、网络状态、超时这几个元素,返回一个数组对象。

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

    self.stringEncoding = NSUTF8StringEncoding;

    self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];

    /*
     枚举系统的language列表。然后设置`Accept-Language`请求头域。优先级逐级降低,最多五个。
     */
    // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
    NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
    [[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        float q = 1.0f - (idx * 0.1f);
        [acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
        *stop = q <= 0.5f;
    }];
    //数组元素使用`, `分割
    [self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];

    NSString *userAgent = nil;
#if TARGET_OS_IOS
    // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
    userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
#elif TARGET_OS_WATCH
    // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
    userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
    userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
#endif
    if (userAgent) {
        if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
            NSMutableString *mutableUserAgent = [userAgent mutableCopy];
            if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
                userAgent = mutableUserAgent;
            }
        }
        [self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
    }

    //默认参数需要追加到url query后面的的方法
    // 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)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))];
}
......

初始化方法,设置一些默认值,然后对需要观察的属性添加观察者,在后面是需要观察的属性的setter方法,手动触发kvo属性。

中间那么一些方法都很简单,自己看看,就不写了。

接下来就是这章的重点,构建NSMutableRequest

#pragma mark -

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(method);
    NSParameterAssert(URLString);

    NSURL *url = [NSURL URLWithString:URLString];

    NSParameterAssert(url);

    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    mutableRequest.HTTPMethod = method;

    /**
     手动触发需要观察的属性的kvo
     */
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }

    //根据创建的request和参数调用下面的方法创建一个最终的request
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

    return mutableRequest;
}

#pragma mark - AFURLRequestSerialization

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(request);

    NSMutableURLRequest *mutableRequest = [request mutableCopy];

    //用枚举的方式设置请求头
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    NSString *query = nil;
    if (parameters) {
        //如果有自定义的序列化方法就调用自定义的
        if (self.queryStringSerialization) {
            NSError *serializationError;
            query = self.queryStringSerialization(request, parameters, &serializationError);

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

                return nil;
            }
        } else {
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    //默认的序列化方式
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }
    //`GET`、`HEAD`、`DELET`三种方式是需要把参数追加到query字符串后面
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query && query.length > 0) {
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
        }
    } else {
        //一般的POST等方式就设置为HTTPBody
        // #2864: an empty string is a valid x-www-form-urlencoded payload
        if (!query) {
            query = @"";
        }
        //设置Content-Type头域
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    }

    return mutableRequest;
}

上面的方法就是一般情况下的请求(非multipart表单请求)走第一个方法,然后调用AFURLRequestSerialization协议进行参数序列化,拼接query或者设置HTTPBody,这就是一般情况下的request,就构建完成了。

接下来就是我们的重点内容,multipart表单请求,还是先来个例子给大家看一下

//开始
--A4CditForm07uar02yZ34WTsOi5HI0YtW7
Content-Disposition: form-data; name="data"; filename="abc.png"
Content-Type: application/octet-stream
 
......//数据
 
--A4CditForm07uar02yZ34WTsOi5HI0YtW7
Content-Disposition: form-data; name="Upload"
 
Submit Query
--A4CditForm07uar02yZ34WTsOi5HI0YtW7--// 表示body结束了

这就是一个multipart表单请求的格式,先有一个整体的概念,下面再详细讲怎么把参数构建为这种格式。

#pragma mark -
//第一个AFHTTPPart表单构建初始边界,即报文主体的初始边界
static NSString * AFCreateMultipartFormBoundary() {
    return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
}

static NSString * const kAFMultipartFormCRLF = @"\r\n";
//非第一个AFHTTPPart表单构建开始边界
static inline NSString * AFMultipartFormInitialBoundary(NSString *boundary) {
    return [NSString stringWithFormat:@"--%@%@", boundary, kAFMultipartFormCRLF];
}
//非最后一个AFHTTPPart表单构建结束边界
static inline NSString * AFMultipartFormEncapsulationBoundary(NSString *boundary) {
    return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}
//最后一个AFHTTPPart表单构建结束边界
static inline NSString * AFMultipartFormFinalBoundary(NSString *boundary) {
    return [NSString stringWithFormat:@"%@--%@--%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}
//根据文件扩展获取MIME类型
static inline NSString * AFContentTypeForPathExtension(NSString *extension) {
    //根据扩展名获取UTI同一类型标识符
    NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
    //UTI同一类型标识符转换为MIME类型
    NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
    if (!contentType) {
        return @"application/octet-stream";
    } else {
        return contentType;
    }
}

这就是表单边界的构建方法和MIME类型的获取

//序列化multipart表单参数的四个阶段
typedef enum {
    //开始边界阶段
    AFEncapsulationBoundaryPhase = 1,
    //表单头阶段
    AFHeaderPhase                = 2,
    //表单主体阶段
    AFBodyPhase                  = 3,
    //表单结束阶段
    AFFinalBoundaryPhase         = 4,
} AFHTTPBodyPartReadPhase;

@interface AFHTTPBodyPart ()  {
    AFHTTPBodyPartReadPhase _phase;
    NSInputStream *_inputStream;
    unsigned long long _phaseReadOffset;
}
//进入下一阶段
- (BOOL)transitionToNextPhase;
//读取数据流
- (NSInteger)readData:(NSData *)data
           intoBuffer:(uint8_t *)buffer
            maxLength:(NSUInteger)length;
@end

@implementation AFHTTPBodyPart

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }
    //参数序列化进入初始阶段
    [self transitionToNextPhase];

    return self;
}

- (void)dealloc {
    if (_inputStream) {
        [_inputStream close];
        _inputStream = nil;
    }
}
//获取主体数据流
- (NSInputStream *)inputStream {
    if (!_inputStream) {
        if ([self.body isKindOfClass:[NSData class]]) {
            _inputStream = [NSInputStream inputStreamWithData:self.body];
        } else if ([self.body isKindOfClass:[NSURL class]]) {
            _inputStream = [NSInputStream inputStreamWithURL:self.body];
        } else if ([self.body isKindOfClass:[NSInputStream class]]) {
            _inputStream = self.body;
        } else {
            _inputStream = [NSInputStream inputStreamWithData:[NSData data]];
        }
    }

    return _inputStream;
}
//multipart表单头的字符串
- (NSString *)stringForHeaders {
    NSMutableString *headerString = [NSMutableString string];
    for (NSString *field in [self.headers allKeys]) {
        [headerString appendString:[NSString stringWithFormat:@"%@: %@%@", field, [self.headers valueForKey:field], kAFMultipartFormCRLF]];
    }
    [headerString appendString:kAFMultipartFormCRLF];

    return [NSString stringWithString:headerString];
}
//表单大小
- (unsigned long long)contentLength {
    unsigned long long length = 0;
    //读取初始边界长度
    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;
    //  表单结束长度
    NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]);
    length += [closingBoundaryData length];

    return length;
}
//是否有数据可读
- (BOOL)hasBytesAvailable {
    // Allows `read:maxLength:` to be called again if `AFMultipartFormFinalBoundary` doesn't fit into the available buffer
    if (_phase == AFFinalBoundaryPhase) {
        return YES;
    }

    //数据流还没打开、已经打开、正在读、正在写,都代表还有数据可以读
    //数据流已经结束、已经关闭、出现错误表示没有数据可以读了
    switch (self.inputStream.streamStatus) {
        case NSStreamStatusNotOpen:
        case NSStreamStatusOpening:
        case NSStreamStatusOpen:
        case NSStreamStatusReading:
        case NSStreamStatusWriting:
            return YES;
        case NSStreamStatusAtEnd:
        case NSStreamStatusClosed:
        case NSStreamStatusError:
        default:
            return NO;
    }
}
//上传时需要读区数据的时候,AFMultipartBodyStream调用此方法
- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length
{
    NSInteger totalNumberOfBytesRead = 0;
    //初始阶段,构建初始边界
    if (_phase == AFEncapsulationBoundaryPhase) {
        NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding];
        totalNumberOfBytesRead += [self readData:encapsulationBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
    }
    //表单头阶段:构建表单头
    if (_phase == AFHeaderPhase) {
        //设置的表单头域进行编码
        NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding];
        //编码后的表单头域写入数据流
        totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
    }
    //表单主体阶段:设置表单主体
    if (_phase == AFBodyPhase) {
        NSInteger numberOfBytesRead = 0;
        //把数据流的内容写入
        numberOfBytesRead = [self.inputStream read:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
        if (numberOfBytesRead == -1) {
            return -1;
        } else {
            totalNumberOfBytesRead += numberOfBytesRead;
            //判断数据流是否写入完成,结束后进入下一阶段
            if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) {
                [self transitionToNextPhase];
            }
        }
    }
    //结束阶段:构建结束边界
    if (_phase == AFFinalBoundaryPhase) {
        //构建结束边界
        NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]);
        //结束边界写入数据流
        totalNumberOfBytesRead += [self readData:closingBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
    }

    return totalNumberOfBytesRead;
}

//判断数据的大小是否超过了表单允许的最大数值,选取两个较小的一个
- (NSInteger)readData:(NSData *)data
           intoBuffer:(uint8_t *)buffer
            maxLength:(NSUInteger)length
{
    NSRange range = NSMakeRange((NSUInteger)_phaseReadOffset, MIN([data length] - ((NSUInteger)_phaseReadOffset), length));
    [data getBytes:buffer range:range];

    _phaseReadOffset += range.length;

    //如果读入的数据已经超过了
    if (((NSUInteger)_phaseReadOffset) >= [data length]) {
        [self transitionToNextPhase];
    }

    return (NSInteger)range.length;
}
//进入下一阶段
- (BOOL)transitionToNextPhase {
    if (![[NSThread currentThread] isMainThread]) {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self transitionToNextPhase];
        });
        return YES;
    }

    switch (_phase) {
        case AFEncapsulationBoundaryPhase:
            _phase = AFHeaderPhase;
            break;
        case AFHeaderPhase:
            //要进入下一阶段(AFBodyPhase),先打开数据流,把数据流加入runloop的伪模式
            [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
            [self.inputStream open];
            _phase = AFBodyPhase;
            break;
        case AFBodyPhase:
            [self.inputStream close];
            _phase = AFFinalBoundaryPhase;
            break;
        case AFFinalBoundaryPhase:
        default:
            _phase = AFEncapsulationBoundaryPhase;
            break;
    }
    _phaseReadOffset = 0;

    return YES;
}

AFHTTPBodyPart类,该有的注释都有了,具体的就不在这儿多解释了,一个AFHTTPBodyPart对象就是一个表单数据,需要上传数据的时候就调用read:maxLength:方法读取,然后调用contentLength获取数据长度。

//继承自NSInputStream
@interface AFMultipartBodyStream () 
//编码方式
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;
//装在AFHTTPBodyPart对象的数组
@property (readwrite, nonatomic, strong) NSMutableArray *HTTPBodyParts;
//AFHTTPBodyPart对象的迭代器,方便迭代
@property (readwrite, nonatomic, strong) NSEnumerator *HTTPBodyPartEnumerator;
//当前的AFHTTPBodyPart对象
@property (readwrite, nonatomic, strong) AFHTTPBodyPart *currentHTTPBodyPart;
@property (readwrite, nonatomic, strong) NSOutputStream *outputStream;
@property (readwrite, nonatomic, strong) NSMutableData *buffer;
@end

@implementation AFMultipartBodyStream
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1100)
@synthesize delegate;
#endif
@synthesize streamStatus;
@synthesize streamError;

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

    self.stringEncoding = encoding;
    self.HTTPBodyParts = [NSMutableArray array];
    self.numberOfBytesInPacket = NSIntegerMax;

    return self;
}
//设置初始边界和结束边界
- (void)setInitialAndFinalBoundaries {
    if ([self.HTTPBodyParts count] > 0) {
        for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) {
            bodyPart.hasInitialBoundary = NO;
            bodyPart.hasFinalBoundary = NO;
        }
        //第一个AFHTTPBodyPart对象设置初始边界
        [[self.HTTPBodyParts firstObject] setHasInitialBoundary:YES];
        //最后一个设置结束边界
        [[self.HTTPBodyParts lastObject] setHasFinalBoundary:YES];
    }
}
//把AFHTTPBodyPart对象加入到当前对象的HTTPBodyParts数组中
- (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart {
    [self.HTTPBodyParts addObject:bodyPart];
}

- (BOOL)isEmpty {
    return [self.HTTPBodyParts count] == 0;
}

#pragma mark - NSInputStream
//在上传数据的时候系统调用该方法获取数据流
- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length
{
    if ([self streamStatus] == NSStreamStatusClosed) {
        return 0;
    }

    NSInteger totalNumberOfBytesRead = 0;
    //读入数据
    while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
        if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
            if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
                break;
            }
        } else {
            //去当前参数大小和数据包最大值中较小的的一个作为数据包大小
            NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
            //调用AFHTTPBodyPart对象的read:maxLength:方法读取数据
            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];
                }
            }
        }
    }
    NSLog(@"buffer : %s  contentLength: %zd", buffer, length);
    return totalNumberOfBytesRead;
}

- (BOOL)getBuffer:(__unused uint8_t **)buffer
           length:(__unused NSUInteger *)len
{
    return NO;
}
//是否还有可读数据
- (BOOL)hasBytesAvailable {
    return [self streamStatus] == NSStreamStatusOpen;
}

AFMultipartBodyStream对象在网络请求发送报文的时候以流的形式作为报文主体,它的内部包含多个AFHTTPBodyPart对象,系统读取AFMultipartBodyStream对象的数据时,AFMultipartBodyStream对象就会去读取AFHTTPBodyPart对象的数据。
AFMultipartBodyStream对象设置了哪些AFHTTPBodyPart是需要设置开始边界,哪些需要设置结束边界,还规定了一个表单的最大长度等数据。

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

    NSString *fileName = [fileURL lastPathComponent];
    //根据文件扩展名MIME类型
    NSString *mimeType = AFContentTypeForPathExtension([fileURL pathExtension]);
    return [self appendPartWithFileURL:fileURL name:name fileName:fileName mimeType:mimeType error:error];
}

- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                         name:(NSString *)name
                     fileName:(NSString *)fileName
                     mimeType:(NSString *)mimeType
                        error:(NSError * __autoreleasing *)error
{
    NSParameterAssert(fileURL);
    NSParameterAssert(name);
    NSParameterAssert(fileName);
    NSParameterAssert(mimeType);
    //文件路径出错
    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;
    }
    //设置表单Content-Disposition和Content-Type头域
    NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
    [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
    [mutableHeaders setValue:mimeType forKey:@"Content-Type"];
    //构建AFHTTPBodyPart对象表单
    AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
    bodyPart.stringEncoding = self.stringEncoding;
    bodyPart.headers = mutableHeaders;
    bodyPart.boundary = self.boundary;
    bodyPart.body = fileURL;
    bodyPart.bodyContentLength = [fileAttributes[NSFileSize] unsignedLongLongValue];
    //把AFHTTPBodyPart对象装载到AFMultipartBodyStream对象中
    [self.bodyStream appendHTTPBodyPart:bodyPart];

    return YES;
}
//和上面的一样,就不写了
.....

/**
 限制数据包的大小和设置一个延时读取。
 是为了防止你数据包内容过大,超过了请求体正文的大小,而导致失败
 */
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
                                  delay:(NSTimeInterval)delay
{
    self.bodyStream.numberOfBytesInPacket = numberOfBytes;
    self.bodyStream.delay = delay;
}

- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
    if ([self.bodyStream isEmpty]) {
        return self.request;
    }

    // 重制初始边界和结束边界以便获取正确的报文大小
    [self.bodyStream setInitialAndFinalBoundaries];
    //把装载AFTHTTPPart对象装载完成后的AFMultipartBodyStream对象设置为报文主体
    [self.request setHTTPBodyStream:self.bodyStream];
    
    //设置请求的Content-Type和Content-Length头域
    [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"];
    [self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"];

    return self.request;
}

AFStreamingMultipartFormData类就是一个中间人,外部参数通过这个类把构建为AFHTTPBodyPart对象,然后把AFHTTPBodyPart写入到AFMultipartBodyStream对象数据流中,在参数写入数据流完成之后调用requestByFinalizingMultipartFormData方法来设置请求体,最后设置请求头类型和请求体大小,request创建完成。

你可能感兴趣的:(AFNetworking知识点之AFURLRequestSerialization)