AFNetWorking源码之AFURLRequestSerialization

Posted by 黄成都 on 2017-04-25
Words 5.3k and Reading Time 25 Minutes
Viewed Times

1 概述

AFURLRequestSerialization主要实现了根据不同情况和参数初始化NSURLRequest对象的功能。只有AFHTTPSessionManager有requestSerialization,默认是AFHTTPRequestSerializer对象。尤其是我们使用MultipartForm请求的时候,可以使用它帮我们完成繁杂的请求头拼接过程,这个是最值得推荐的。

在阅读源码之前,一定要对multipart/form-data非常熟悉,不然会有很多地方看不懂。具体可以看AFNetWorking源码之AFHTTPSessionManager关于它的那部分。

2 AFURLRequestSerialization的api分析

AFURLRequestSerialization包含了四个部分:

  • 全局方法:AFPercentEscapedStringFromStringAFQueryStringFromParameters
  • 协议AFURLRequestSerialization提供了一个序列化parameters参数的方法。我们可以把参数转换为查询字符串、HTTP请求体、设置恰当的请求头等。
  • AFHTTPRequestSerializer继承自AFURLRequestSerialization协议。提供了查询字符串/URL格式的参数序列化、默认请求头处理。同时以提供HTTP状态码和返回数据的验证等工作。
    _ AFMultipartFormData协议。主要用于添加multipart/form-data请求的Content-Disposition: file; filename=#{generated filename}; name=#{name}"Content-Type: #{generated mimeType}的请求体域。
  • 类型AFJSONRequestSerializerAFPropertyListRequestSerializer。主要针对JSON和Plist类型的序列化优化。

AFPercentEscapedStringFromString返回一个字符串的百分号编码格式的字符串。因为url只有普通英文字符和数字,特殊字符$-_.+!*’()还有保留字符。所以很多字符都需要编码,非ASCII编码的字符串先转换为ASCII编码,然后再转换为百分号编码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
AFPercentEscapedStringFromString方法的作用就是把一个普通字符串转换为百分号编码的字符串
http://blog.csdn.net/qq_32010299/article/details/51790407
@param string 一个字符串
@return 百分号编码的字符串
*/
NSString * AFPercentEscapedStringFromString(NSString *string) {
//可能需要做百分号编码处理的字符串
static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@";
static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
//不需要做百分号编码的字符串集合
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
//获取目前系统中最终需要做百分号编码转换的字符集合
[allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];

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 = [string rangeOfComposedCharacterSequencesForRange:range];
NSString *substring = [string substringWithRange:range];
//指定范围内的字符做百分号编码
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
[escaped appendString:encoded];
index += range.length;
}
//返回处理以后的字符串
return escaped;
}

私有类AFQueryStringPair的主要功能就是把一个key和vaue的键值对转换为百分号编码格式的键值对并且用=链接起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@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;
}
/**
把key、value键值对转换为百分号编码,并且链接起来
@return 转换后的字符串
*/
- (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

方法AFQueryStringPairsFromDictionaryAFQueryStringPairsFromKeyAndValue分别把一个字典或者key、value键值对转换为url的query参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
把一个字典转换为百分号编码的query参数
@param parameters 要转换的字典
@return query参数
*/
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
//调用`AFQueryStringPair`序列化
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
/**
分别把一个字典、数组、集合转换为一个AFQueryStringPair对象的的数组。
@param key key
@param value value
@return AFQueryStringPair类型数组
*/
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
//使用`description`排序
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
//如果是字典,就取出每一对key、value处理
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
for (id nestedValue in array) {
//如果是数组,则取出元素,添加一个额外的key处理
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
//如果是集合,就是用默认key和集合元素处理
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {
//添加处理后的key和value
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
//返回`AFQueryStringPair`对象数组
return mutableQueryStringComponents;
}

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

1
2
3
4
5
6
7
8
9
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;
}

2.1 AFHTTPRequestSerializer的解析

AFHTTPRequestSerializer主要实现了大部分request拼接转化功能。比如通用请求头的添加如userAgent、request属性的KVO观察、手动指定请求头序列化的Block、负责具体的request对象的初始化等。

1 AFHTTPRequestSerializer的属性和初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//属性列表
@interface AFHTTPRequestSerializer ()
//某个request需要观察的属性集合
@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;
//存储request的请求头域
@property (readwrite, nonatomic, strong) NSMutableDictionary *mutableHTTPRequestHeaders;
//用于修改或者设置请求体域的dispatch_queue_t。
@property (readwrite, nonatomic, strong) dispatch_queue_t requestHeaderModificationQueue;
@property (readwrite, nonatomic, assign) AFHTTPRequestQueryStringSerializationStyle queryStringSerializationStyle;
//手动指定parameters参数序列化的Block
@property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization;
@end
//初始化方法
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
//指定序列化编码格式
self.stringEncoding = NSUTF8StringEncoding;
//请求头保存在一个字典中,方便后面构建request的时候拼装。
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
//初始化一个操作request的header域的dispatch_queue_t
self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);

NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
/*
*枚举系统的language列表。然后设置`Accept-Language`请求头域。优先级逐级降低,最多五个。
*/
[[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"];
/*
*设置User-Agent请求头域的值。
*/
NSString *userAgent = nil;
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]];
if (userAgent) {
/*
*如果userAgent里面包含非ASCII码的字符,比如中文,则需要转换。这里是转换为对应的拉丁字母。
AFNetWorking3.X源码阅读/1.0 (iPhone; iOS 10.2; Scale/2.00)
AFNetWorking3.X yuan ma yue du/1.0 (iPhone; iOS 10.2; Scale/2.00)
*/
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
//需要把parameters转换为query参数的方法集合。
self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];

self.mutableObservedChangedKeyPaths = [NSMutableSet set];
/*
添加对蜂窝数据、缓存策略、cookie、管道、网络状态、超时这几个属性的观察。
*/
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
return self;
}

2 AFHTTPRequestSerializer的各种setter方法

首先通过automaticallyNotifiesObserversForKey方法来阻止一些属性的KVO机制的触发,然后我们通过重写蜂窝数据、缓存策略、cookie、管道、网络状态、超时的观察。可以用于测试这些属性变化是否崩溃等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
如果kvo的触发机制是默认出发。则返回true,否则返回false。在这里,只要是`AFHTTPRequestSerializerObservedKeyPaths`里面的属性,我们都取消自动出发kvo机制,使用手动触发。

@param key kvo的key
@return bool值
*/
+ (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) {
//如果属性值为null,则表示么有这个属性,移除对其的观察
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
} else {
//添加到要观察的属性的集合
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}

通过重写属性的setter方法来手动触发kvo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#pragma mark - 手动触发蜂窝数据、缓存策略、cookie、管道、网络状态、超时的观察。可以用于测试这些属性变化是否崩溃等。
- (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))];
}

3 AFHTTPRequestSerializer的各种请求头域处理方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
返回请求头域key和vaue

@return 字典
*/
- (NSDictionary *)HTTPRequestHeaders {
NSDictionary __block *value;
dispatch_sync(self.requestHeaderModificationQueue, ^{
value = [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
});
return value;
}

/**
设置一个请求头域

@param value vaue
@param field 域名
*/
- (void)setValue:(NSString *)value
forHTTPHeaderField:(NSString *)field
{
dispatch_barrier_async(self.requestHeaderModificationQueue, ^{
[self.mutableHTTPRequestHeaders setValue:value forKey:field];
});
}
/**
返回指定请求头域的值

@param field 域名
@return 值
*/
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
NSString __block *value;
dispatch_sync(self.requestHeaderModificationQueue, ^{
value = [self.mutableHTTPRequestHeaders valueForKey:field];
});
return value;
}

/**
设置Basic Authorization的用户名和密码。记住需要是base64编码格式的。
@param username 用户
@param password 密码
*/
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
password:(NSString *)password
{
NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
[self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"];
}
/**
移除Basic Authorization的请求头
*/
- (void)clearAuthorizationHeader {
dispatch_barrier_async(self.requestHeaderModificationQueue, ^{
[self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"];
});
}

4 AFHTTPRequestSerializer的各种创建NSMutableURLRequest的方法

通过下面这三种方法处理不同类型的request对象的初始化和参数序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/**
根据给定的url、方法名、参数构建一个request。

@param method 方法名
@param URLString url地址
@param parameters 参数,根据不同的请求方法构建出不同的模式
@param error 构建出错
@return 返回一个非multipartForm请求
*/
- (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;
/*
*mutableObservedChangedKeyPaths集合里面的属性都通过`setValue: forKey`手动设置一下。估计目的是触发这几个属性的kvo。
*/
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
/*
根据parameters和HTTPRequestHeaders构建一个request
*/
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}

/**
构建一个multipartForm的request。并且通过`AFMultipartFormData`类型的formData来构建请求体

@param method 方法名,一般都是POST
@param URLString 请求地址
@param parameters 请求头参数
@param block 用于构建请求体的Block
@param error 构建请求体出错
@return 返回一个构建好的request
*/
- (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"]);
/*
先构建一个普通的request对象,然后在构建出multipartFrom的request
* 在这一步将会把parameters加入请求头或者请求体。然后把`AFURLRequestSerialization`指定的headers加入request的请求头中。这个request就只差构建multipartFrom部分了
*/
NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];
/*
*初始化一个`AFStreamingMultipartFormData`对象。用于封装multipartFrom的body部分
*/
__block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
if (parameters) {
/*
把parameters拼接成`AFQueryStringPair`对象。然后根据取出的key和value处理。
*/
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
NSData *data = nil;
//把value处理为NSData类型
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) {
[formData appendPartWithFormData:data name:[pair.field description]];
}
}
}
if (block) {
block(formData);
}
//body具体序列化操作
return [formData requestByFinalizingMultipartFormData];
}

/**
通过一个Multipart-Form的request创建一个request。新request的httpBody是`fileURL`指定的文件。
并且是通过`HTTPBodyStream`这个属性添加,`HTTPBodyStream`属性的数据会自动添加为httpBody。

@param request 原request
@param fileURL 文件的url
@param handler 错误处理
@return 处理完成的request
*/
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
writingStreamContentsToFile:(NSURL *)fileURL
completionHandler:(void (^)(NSError *error))handler
{
NSParameterAssert(request.HTTPBodyStream);
NSParameterAssert([fileURL isFileURL]);
//获取`HTTPBodyStream`属性
NSInputStream *inputStream = request.HTTPBodyStream;
//获取文件的数据流
NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO];
__block NSError *error = nil;

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//把读和写的操作加入当前线程的runloop
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
//打开读和写数据流
[inputStream open];
[outputStream open];
//循环做读和写操作
while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) {
uint8_t buffer[1024];

NSInteger bytesRead = [inputStream read:buffer maxLength:1024];
if (inputStream.streamError || bytesRead < 0) {
error = inputStream.streamError;
break;
}

NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead];
if (outputStream.streamError || bytesWritten < 0) {
error = outputStream.streamError;
break;
}

if (bytesRead == 0 && bytesWritten == 0) {
break;
}
}
//读和写完成。关闭读和写数据流
[outputStream close];
[inputStream close];
//如果有handler,调用handler这个Block
if (handler) {
dispatch_async(dispatch_get_main_queue(), ^{
handler(error);
});
}
});
//获取一个新的request,新的request的httpBody已经通过`HTTPBodyStream`转换成功
NSMutableURLRequest *mutableRequest = [request mutableCopy];
mutableRequest.HTTPBodyStream = nil;
//返回一个request对象
return mutableRequest;
}

3 AFStreamingMultipartFormData私有类的解析

首先,我们看几个全局方法。下面几个方法用于拼接multipart/form-data的分隔符和文件的MIMEType

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
生成multipartForm的request的boundary
*/
static NSString * AFCreateMultipartFormBoundary() {
return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
}
//回车换行符
static NSString * const kAFMultipartFormCRLF = @"\r\n";
//生成一个request的请求体中的参数的开始符号,第一个
static inline NSString * AFMultipartFormInitialBoundary(NSString *boundary) {
return [NSString stringWithFormat:@"--%@%@", boundary, kAFMultipartFormCRLF];
}
//生成一个request的请求体中的参数的开始符号,菲第一个。
static inline NSString * AFMultipartFormEncapsulationBoundary(NSString *boundary) {
return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}
//生成一个request的请求体中的参数的结束符号
static inline NSString * AFMultipartFormFinalBoundary(NSString *boundary) {
return [NSString stringWithFormat:@"%@--%@--%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}
/*
根据文件的扩展名获取文件的`MIMEType`
*/
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;
}
}

AFStreamingMultipartFormData负责multipart/form-data的Body的具体构建。比如boundary的指定、请求体数据的拼接等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
- (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest
stringEncoding:(NSStringEncoding)encoding
{
self = [super init];
if (!self) {
return nil;
}
//需要添加httpbody的request
self.request = urlRequest;
//字符编码
self.stringEncoding = encoding;
//指定boundary
self.boundary = AFCreateMultipartFormBoundary();
//这个属性用于存储httpbody数据
self.bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding];
return self;
}
/*
根据文件的url添加一个`multipart/form-data`请求的请求体域
*/
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
error:(NSError * __autoreleasing *)error
{
NSParameterAssert(fileURL);
NSParameterAssert(name);
//文件扩展名
NSString *fileName = [fileURL lastPathComponent];
//获取文件的mimetype的类型
NSString *mimeType = AFContentTypeForPathExtension([fileURL pathExtension]);

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

/**
根据指定类型的fileurl,把数据添加进入bodyStream中。以提供给后面构建request的body。

@param fileURL 文件的url
@param name 参数名称
@param fileName 文件名称
@param mimeType 文件类型
@param error 错误
@return 是否成功
*/
- (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];
[self.bodyStream appendHTTPBodyPart:bodyPart];

return YES;
}
/**
根据指定类型的数据流,把数据添加进入bodyStream中。以提供给后面构建request的body。

@param inputStream 输入的数据流
@param name 参数名称
@param fileName 文件名称
@param mimeType 文件类型
*/
- (void)appendPartWithInputStream:(NSInputStream *)inputStream
name:(NSString *)name
fileName:(NSString *)fileName
length:(int64_t)length
mimeType:(NSString *)mimeType
{
NSParameterAssert(name);
NSParameterAssert(fileName);
NSParameterAssert(mimeType);
//添加`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 = inputStream;
bodyPart.bodyContentLength = (unsigned long long)length;
[self.bodyStream appendHTTPBodyPart:bodyPart];
}

/**
根据指定的data添加到请求体域中

@param data 数据
@param name 名称
@param fileName 文件名称
@param mimeType mimeType
*/
- (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];
}

/**
根据指定的key和value拼接到`Content-Disposition`属性中

@param data 参数值
@param name 参数名
*/
- (void)appendPartWithFormData:(NSData *)data
name:(NSString *)name
{
NSParameterAssert(name);

NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"];
//把处理好的数据加入对应的request的请求体中`Content-Disposition`部分
[self appendPartWithHeaders:mutableHeaders body:data];
}

/**
给一个multipartForm的`Content-Disposition`添加boundary

@param headers 请求头域
@param body 值
*/
- (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;
}

/**
根据一个request对应的`AFStreamingMultipartFormData`对象获取封装好的request对象

@return multipart/form的request对象
*/
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
if ([self.bodyStream isEmpty]) {
return self.request;
}
// Reset the initial and final boundaries to ensure correct Content-Length
//重置boundary,从而确保`Content-Length`正确
[self.bodyStream setInitialAndFinalBoundaries];
//把拼接好的bodyStream添加进入request中
[self.request setHTTPBodyStream:self.bodyStream];
//给requst的请求头添加Content-Type属性指定为`multipart/form-data`类型的request。同时设置请求体的长度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;
}

4 AFJSONRequestSerializerAFPropertyListRequestSerializer

这两个类继承自AFHTTPRequestSerializer。他们的基本实现都是继承自父类。但是也根据自身不同情况,做了处理。

对于AFJSONRequestSerializer。需要把Content-Type指定为"application/json。同时HTTPBody
需要使用JSON序列化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
/*
对于`GET`,`HEAD`,`DELETE`等方法中。直接使用父类的处理方式
*/
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
return [super requestBySerializingRequest:request withParameters:parameters error:error];
}
NSMutableURLRequest *mutableRequest = [request mutableCopy];
//把`HTTPRequestHeaders`中的值添加进入请求头中。
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
if (parameters) {
//设置请求头的`Content-Type`类型
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
}

if (![NSJSONSerialization isValidJSONObject:parameters]) {
if (error) {
NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"The `parameters` argument is not valid JSON.", @"AFNetworking", nil)};
*error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo];
}
return nil;
}
//把parameters转换为JSON序列化的data
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error];
if (!jsonData) {
return nil;
}
//JSON序列化的数据设置为httpbody
[mutableRequest setHTTPBody:jsonData];
}
return mutableRequest;
}

对于AFPropertyListRequestSerializer也是同样的道理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
/*
对于`GET`,`HEAD`,`DELETE`等方法中。直接使用父类的处理方式
*/
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
return [super requestBySerializingRequest:request withParameters:parameters error:error];
}
NSMutableURLRequest *mutableRequest = [request mutableCopy];
//把`HTTPRequestHeaders`中的值添加进入请求头中。
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
if (parameters) {
//设置请求头的`Content-Type`类型
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"];
}
//把parameters转换为Plist序列化的data
NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error];
if (!plistData) {
return nil;
}
//Plist序列化的数据设置为httpbody
[mutableRequest setHTTPBody:plistData];
}
return mutableRequest;
}

5 总结

这个类主要实现了对于不同情况的请求的request对象的封装。尤其是对于multipart/form-data类型的request的封装,简化了我们自己封装过程的痛苦。如果我们要使用multipart/form-data类型的请求。强烈推荐使用AFHTTPSessionManager对象的AFHTTPRequestSerialization来处理参数的序列化过程。下面就是使用AFHTTPRequestSerailization序列化和自己拼装的不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
- (IBAction)updatePic:(id)sender {
//请求头参数
NSDictionary *dic = @{
@"businessType":@"CC_USER_CENTER",
@"fileType":@"image",
@"file":@"img.jpeg"
};
//请求体图片数据
NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:@"1.png"]);
//创建request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:url]];
//post方法
[request setHTTPMethod:@"POST"];
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDataTask *task = [manager POST:url parameters:dic constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
//请求体里面的参数
NSDictionary *bodyDic = @{
@"Content-Disposition":@"form-data;name=\"file\";filename=\"img.jpeg\"",
@"Content-Type":@"image/png",
};
[formData appendPartWithHeaders:bodyDic body:imageData];
} progress:^(NSProgress * _Nonnull uploadProgress) {
NSLog(@"下载进度");
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"下载成功:%@",responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"下载失败%@",error);
}];
[task resume];
}
- (IBAction)multipartformPost3:(id)sender {
//参数
NSDictionary *dic = @{
@"businessType":@"CC_USER_CENTER",
@"fileType":@"image",
@"file":@"img.jpeg"
};
NSString *boundaryString = @"xxxxx";
NSMutableString *str = [NSMutableString string];
[dic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
[str appendFormat:@"--%@\r\n",boundaryString];
[str appendFormat:@"%@name=\"%@\"\r\n\r\n",@"Content-Disposition: form-data;",key];
[str appendFormat:@"%@\r\n",obj];
}];

NSMutableData *requestMutableData=[NSMutableData data];

[str appendFormat:@"--%@\r\n",boundaryString];
[str appendFormat:@"%@:%@",@"Content-Disposition",@"form-data;"];
[str appendFormat:@"%@=\"%@\";",@"name",@"file"];
[str appendFormat:@"%@=\"%@\"\r\n",@"filename",@"img1.jpeg"];
[str appendFormat:@"%@:%@\r\n\r\n",@"Content-Type",@"image/png"];
//转换成为二进制数据
[requestMutableData appendData:[str dataUsingEncoding:NSUTF8StringEncoding]];
NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:@"1.png"]);
//文件数据部分
[requestMutableData appendData:imageData];
//添加结尾boundary
[requestMutableData appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundaryString] dataUsingEncoding:NSUTF8StringEncoding]];


NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:url]];
//post方法
[request setHTTPMethod:@"POST"];
// 设置请求头格式为Content-Type:multipart/form-data; boundary=xxxxx
[request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundaryString] forHTTPHeaderField:@"Content-Type"];
request.HTTPBody = requestMutableData;

NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSString *result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",result);
}];

[task resume];

}

最后原文地址,demo地址


...

...

00:00
00:00