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