1 概述
AFURLRequestSerialization
主要实现了根据不同情况和参数初始化NSURLRequest
对象的功能。只有AFHTTPSessionManager
有requestSerialization,默认是AFHTTPRequestSerializer
对象。尤其是我们使用MultipartForm
请求的时候,可以使用它帮我们完成繁杂的请求头拼接过程,这个是最值得推荐的。
在阅读源码之前,一定要对multipart/form-data
非常熟悉,不然会有很多地方看不懂。具体可以看AFNetWorking源码之AFHTTPSessionManager关于它的那部分。
2 AFURLRequestSerialization的api分析
AFURLRequestSerialization
包含了四个部分:
全局方法:
AFPercentEscapedStringFromString
和AFQueryStringFromParameters
。协议
AFURLRequestSerialization
提供了一个序列化parameters
参数的方法。我们可以把参数转换为查询字符串、HTTP请求体、设置恰当的请求头等。AFHTTPRequestSerializer
继承自AFURLRequestSerialization
协议。提供了查询字符串/URL格式的参数序列化、默认请求头处理。同时以提供HTTP状态码和返回数据的验证等工作。
_AFMultipartFormData
协议。主要用于添加multipart/form-data
请求的Content-Disposition: file; filename=#{generated filename}; name=#{name}"
和Content-Type: #{generated mimeType}
的请求体域。类型
AFJSONRequestSerializer
和AFPropertyListRequestSerializer
。主要针对JSON和Plist类型的序列化优化。
AFPercentEscapedStringFromString
返回一个字符串的百分号编码格式的字符串。因为url只有普通英文字符和数字,特殊字符$-_.+!*'()还有保留字符。所以很多字符都需要编码,非ASCII编码的字符串先转换为ASCII编码,然后再转换为百分号编码。
/**
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的键值对转换为百分号编码格式的键值对并且用=链接起来
@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
方法AFQueryStringPairsFromDictionary
和AFQueryStringPairsFromKeyAndValue
分别把一个字典或者key、value键值对转换为url的query参数。
/**
把一个字典转换为百分号编码的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、管道、网络状态、超时这几个元素。
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
的属性和初始化
//属性列表
@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、管道、网络状态、超时的观察。可以用于测试这些属性变化是否崩溃等。
/**
如果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
#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
的各种请求头域处理方法
/**
返回请求头域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对象的初始化和参数序列化。
/**
根据给定的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 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
。
/*
生成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的指定、请求体数据的拼接等。
- (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 AFJSONRequestSerializer
和AFPropertyListRequestSerializer
这两个类继承自AFHTTPRequestSerializer
。他们的基本实现都是继承自父类。但是也根据自身不同情况,做了处理。
对于AFJSONRequestSerializer
。需要把Content-Type
指定为"application/json
。同时HTTPBody
需要使用JSON序列化:
- (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
也是同样的道理:
- (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
序列化和自己拼装的不同:
- (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 _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];
}