目录
- 前言
- 流程图
- 核心代码
- 请求头
- 请求体
- 1、通过Parameters(参数字典)构建的请求体
- 1、用户自定义block转译
- 2、AFN默认的转义方式:
- 将字典转化成字符串并且转译
- 根据不同情况将参数字符串拼接到URL后、或者放入请求体
- 2、通过文件构建的请求体
- AFMultipartFormData协议
- AFHTTPBodyPart
-
AFStreamingMultipartFormData
- 核心方法(将文件分段拼接)
- 1、通过Parameters(参数字典)构建的请求体
- AFHTTPRequestSerializer
- AFHTTPRequestSerializer.h
- AFHTTPRequestSerializer.m
- NSInputStream和NSOutputSteam的使用
- APIDemo
- 参考资料
前言
AFNetworking源码第四篇
主要看了看AFURLRequestSerialization的内容
负责网络请求NSMutableURLRequest对象的初始化
以及请求头、请求体、参数、上传文件的自动化配置
几千行代码、很长。但是读下来会受益匪浅
AFN概述:《iOS源码补完计划--AFNetworking 3.1.0源码研读》
流程图
里面的文件实在是太多了、还是先弄个流程图好一些?
解释图中几个相关的类:
- 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请求分析