AFURLRequestSerialization
主要实现了根据不一样状况和参数初始化NSURLRequest
对象的功能。只有AFHTTPSessionManager
有requestSerialization,默认是AFHTTPRequestSerializer
对象。尤为是咱们使用MultipartForm
请求的时候,可使用它帮咱们完成繁杂的请求头拼接过程,这个是最值得推荐的。html
在阅读源码以前,必定要对multipart/form-data
很是熟悉,否则会有不少地方看不懂。具体能够看AFNetWorking源码之AFHTTPSessionManager关于它的那部分。git
AFURLRequestSerialization
包含了四个部分:github
全局方法:AFPercentEscapedStringFromString
和AFQueryStringFromParameters
。json
协议AFURLRequestSerialization
提供了一个序列化parameters
参数的方法。咱们能够把参数转换为查询字符串、HTTP请求体、设置恰当的请求头等。api
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编码,而后再转换为百分号编码。cookie
/** 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参数。session
/** 把一个字典转换为百分号编码的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; }
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 <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; }
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; }
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; }
这个类主要实现了对于不一样状况的请求的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<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]; }